From 9785ebbe1725b2d4991d86986bdfcab543748f31 Mon Sep 17 00:00:00 2001 From: David Hyrule Date: Mon, 29 Jun 2026 21:14:16 +0200 Subject: [PATCH 1/2] feat: add agentic observatory contracts --- agent_core/contracts/__init__.py | 47 + agent_core/contracts/observatory.py | 468 ++++++ .../contracts/schemas/ActorRef.schema.json | 46 + .../contracts/schemas/CaseSummary.schema.json | 200 +++ .../schemas/ChangeImpactReport.schema.json | 527 +++++++ .../contracts/schemas/ChangeRef.schema.json | 163 +++ .../schemas/HandoffSummary.schema.json | 176 +++ .../schemas/ImpactWindow.schema.json | 48 + .../KnowledgeArtifactSummary.schema.json | 157 ++ .../schemas/LoopDescriptor.schema.json | 168 +++ .../schemas/LoopRuntimeSnapshot.schema.json | 154 ++ .../schemas/LoopTopology.schema.json | 268 ++++ .../contracts/schemas/MetricDelta.schema.json | 104 ++ .../ObservatoryActionRequest.schema.json | 106 ++ .../ObservatoryActionResult.schema.json | 136 ++ .../schemas/ObservatoryLink.schema.json | 52 + .../schemas/ObservatorySnapshot.schema.json | 1257 +++++++++++++++++ .../schemas/OutcomeSummary.schema.json | 145 ++ .../contracts/schemas/RunSummary.schema.json | 237 ++++ .../schemas/ScoreComponent.schema.json | 202 +++ .../schemas/TimelineItem.schema.json | 255 ++++ .../schemas/TopologyEdge.schema.json | 138 ++ .../schemas/TopologyNode.schema.json | 147 ++ .../VerificationObjectiveSummary.schema.json | 179 +++ tests/contracts/test_observatory_contracts.py | 173 +++ 25 files changed, 5553 insertions(+) create mode 100644 agent_core/contracts/observatory.py create mode 100644 agent_core/contracts/schemas/ActorRef.schema.json create mode 100644 agent_core/contracts/schemas/CaseSummary.schema.json create mode 100644 agent_core/contracts/schemas/ChangeImpactReport.schema.json create mode 100644 agent_core/contracts/schemas/ChangeRef.schema.json create mode 100644 agent_core/contracts/schemas/HandoffSummary.schema.json create mode 100644 agent_core/contracts/schemas/ImpactWindow.schema.json create mode 100644 agent_core/contracts/schemas/KnowledgeArtifactSummary.schema.json create mode 100644 agent_core/contracts/schemas/LoopDescriptor.schema.json create mode 100644 agent_core/contracts/schemas/LoopRuntimeSnapshot.schema.json create mode 100644 agent_core/contracts/schemas/LoopTopology.schema.json create mode 100644 agent_core/contracts/schemas/MetricDelta.schema.json create mode 100644 agent_core/contracts/schemas/ObservatoryActionRequest.schema.json create mode 100644 agent_core/contracts/schemas/ObservatoryActionResult.schema.json create mode 100644 agent_core/contracts/schemas/ObservatoryLink.schema.json create mode 100644 agent_core/contracts/schemas/ObservatorySnapshot.schema.json create mode 100644 agent_core/contracts/schemas/OutcomeSummary.schema.json create mode 100644 agent_core/contracts/schemas/RunSummary.schema.json create mode 100644 agent_core/contracts/schemas/ScoreComponent.schema.json create mode 100644 agent_core/contracts/schemas/TimelineItem.schema.json create mode 100644 agent_core/contracts/schemas/TopologyEdge.schema.json create mode 100644 agent_core/contracts/schemas/TopologyNode.schema.json create mode 100644 agent_core/contracts/schemas/VerificationObjectiveSummary.schema.json create mode 100644 tests/contracts/test_observatory_contracts.py diff --git a/agent_core/contracts/__init__.py b/agent_core/contracts/__init__.py index d0ede4b..0d0237c 100644 --- a/agent_core/contracts/__init__.py +++ b/agent_core/contracts/__init__.py @@ -25,6 +25,30 @@ from agent_core.contracts.feedback import FeedbackEvent from agent_core.contracts.graph import EdgeMode, EdgeSpec, GraphSpec, NodeKind, NodeSpec from agent_core.contracts.models import CostUsage, ModelPolicy, RoutingDecision +from agent_core.contracts.observatory import ( + ActorRef, + CaseSummary, + ChangeImpactReport, + ChangeRef, + HandoffSummary, + ImpactWindow, + KnowledgeArtifactSummary, + LoopDescriptor, + LoopRuntimeSnapshot, + LoopTopology, + MetricDelta, + ObservatoryActionRequest, + ObservatoryActionResult, + ObservatoryLink, + ObservatorySnapshot, + OutcomeSummary, + RunSummary, + ScoreComponent, + TimelineItem, + TopologyEdge, + TopologyNode, + VerificationObjectiveSummary, +) from agent_core.contracts.state import AgentState from agent_core.contracts.task import TaskEnvelope from agent_core.contracts.tools import ToolContract, ToolResult @@ -61,6 +85,29 @@ "PolicyGateResult", "FeedbackEvent", "ErrorEnvelope", + # observatory + "ObservatoryLink", + "ActorRef", + "LoopDescriptor", + "LoopRuntimeSnapshot", + "RunSummary", + "TimelineItem", + "CaseSummary", + "HandoffSummary", + "VerificationObjectiveSummary", + "KnowledgeArtifactSummary", + "OutcomeSummary", + "TopologyNode", + "TopologyEdge", + "LoopTopology", + "ChangeRef", + "ImpactWindow", + "MetricDelta", + "ScoreComponent", + "ChangeImpactReport", + "ObservatoryActionRequest", + "ObservatoryActionResult", + "ObservatorySnapshot", # graph (draft) "GraphSpec", "NodeSpec", diff --git a/agent_core/contracts/observatory.py b/agent_core/contracts/observatory.py new file mode 100644 index 0000000..5a479ca --- /dev/null +++ b/agent_core/contracts/observatory.py @@ -0,0 +1,468 @@ +"""Agentic Observatory DTOs shared by trace collectors, loop APIs, and UI clients. + +These contracts intentionally describe *query/read models* and audited UI action +requests/results. They do not own operational state: NOC CaseService/LHP-v1 and +Agent-Core trace storage remain the authoritative sources. The observatory can +cache these shapes, render them server-side, and hand them to TypeScript +visualization islands without inventing a second state machine. +""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal +from uuid import uuid4 + +from pydantic import Field + +from agent_core.contracts._base import VersionedModel, utcnow + +LoopKind = Literal[ + "engineering", + "noc", + "knowledge", + "soc", + "collector", + "observability", + "other", +] +LoopStatus = Literal["active", "idle", "degraded", "disabled", "unknown"] +ActionStatus = Literal[ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown", +] +TimelineItemType = Literal[ + "trace_event", + "case_event", + "handoff", + "verification", + "knowledge_artifact", + "outcome", + "approval", + "pr", + "ci", + "deploy", + "operator_feedback", + "analysis", + "other", +] +LinkKind = Literal[ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other", +] +TopologyNodeKind = Literal[ + "loop", + "service", + "collector", + "case", + "handoff", + "repository", + "knowledge_base", + "operator", + "external", + "other", +] +TopologyEdgeKind = Literal[ + "emits_trace", + "reads", + "writes", + "requests_handoff", + "updates_handoff", + "verifies", + "opens_pr", + "deploys", + "notifies", + "depends_on", + "other", +] +ImpactComponentKind = Literal["speed", "quality", "autonomy", "cost", "safety"] +ImpactVerdict = Literal["better", "worse", "mixed", "inconclusive"] +MetricDirection = Literal["higher_is_better", "lower_is_better", "neutral"] +Confidence = Literal["low", "medium", "high", "unknown"] + + +def _observatory_id(prefix: str) -> str: + return f"{prefix}_{uuid4().hex}" + + +class ObservatoryLink(VersionedModel): + """Bounded link/reference from an observatory DTO to its source artifact.""" + + kind: LinkKind = "other" + label: str = "" + url: str = "" + ref_id: str = "" + metadata: dict[str, Any] = Field(default_factory=dict) + + +class ActorRef(VersionedModel): + """Human, loop, service, or system actor shown in audit/timeline UI.""" + + actor_id: str = "" + actor_type: Literal["operator", "loop", "service", "system", "external", "unknown"] = "unknown" + display_name: str = "" + role: str = "" + loop_id: str = "" + + +class LoopDescriptor(VersionedModel): + """Static-ish descriptor for one agentic loop or future loop placeholder.""" + + loop_id: str + display_name: str + kind: LoopKind = "other" + description: str = "" + status: LoopStatus = "unknown" + environment: str = "production" + owner: str = "" + service_name: str = "" + host: str = "" + trace_graph_id: str = "" + capabilities: list[str] = Field(default_factory=list) + input_channels: list[str] = Field(default_factory=list) + output_channels: list[str] = Field(default_factory=list) + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class LoopRuntimeSnapshot(VersionedModel): + """Current operational snapshot for one loop card on the observatory home page.""" + + loop_id: str + status: LoopStatus = "unknown" + summary: str = "" + active_run_id: str = "" + active_case_id: str = "" + active_handoff_id: str = "" + recent_action_count: int = Field(default=0, ge=0) + pending_action_count: int = Field(default=0, ge=0) + failed_action_count: int = Field(default=0, ge=0) + last_event_at: datetime | None = None + checked_at: datetime = Field(default_factory=utcnow) + health: dict[str, Any] = Field(default_factory=dict) + links: list[ObservatoryLink] = Field(default_factory=list) + + +class RunSummary(VersionedModel): + """Trace-backed run/cycle summary assembled from collector rows.""" + + run_id: str + loop_id: str = "" + graph_id: str = "" + trace_id: str = "" + status: ActionStatus = "unknown" + title: str = "" + summary: str = "" + started_at: datetime | None = None + ended_at: datetime | None = None + last_event_at: datetime | None = None + event_count: int = Field(default=0, ge=0) + error_count: int = Field(default=0, ge=0) + cost_usd: float | None = Field(default=None, ge=0.0) + input_tokens: int | None = Field(default=None, ge=0) + output_tokens: int | None = Field(default=None, ge=0) + case_ids: list[str] = Field(default_factory=list) + handoff_ids: list[str] = Field(default_factory=list) + change_ids: list[str] = Field(default_factory=list) + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class TimelineItem(VersionedModel): + """A safe, ordered event for server-rendered timelines and replay islands.""" + + item_id: str = Field(default_factory=lambda: _observatory_id("tl")) + item_type: TimelineItemType + title: str + summary: str = "" + status: ActionStatus = "unknown" + severity: Literal["critical", "high", "medium", "low", "info", "unknown"] = "unknown" + occurred_at: datetime = Field(default_factory=utcnow) + source_loop: str = "" + source_system: str = "" + actor: ActorRef | None = None + parent_item_id: str = "" + run_id: str = "" + trace_event_id: str = "" + case_id: str = "" + handoff_id: str = "" + objective_id: str = "" + change_id: str = "" + repository: str = "" + links: list[ObservatoryLink] = Field(default_factory=list) + payload: dict[str, Any] = Field(default_factory=dict) + + +class CaseSummary(VersionedModel): + """CaseService case projection reduced to the observatory list/detail header.""" + + case_id: str + case_number: str = "" + kind: Literal["atomic", "meta", "unknown"] = "unknown" + status: str = "" + severity: str = "UNKNOWN" + title: str = "" + summary: str = "" + origin: str = "" + resource_id: str = "" + issue_url: str = "" + opened_at: datetime | None = None + updated_at: datetime | None = None + resolved_at: datetime | None = None + trace_ids: list[str] = Field(default_factory=list) + feedback_count: int = Field(default=0, ge=0) + handoff_count: int = Field(default=0, ge=0) + verification_pending_count: int = Field(default=0, ge=0) + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class HandoffSummary(VersionedModel): + """Loop Handoff Protocol state shown without exposing unbounded payloads.""" + + handoff_id: str + case_id: str + source_loop: str = "" + target_loop: str = "" + objective: str = "" + objective_key: str = "" + status: str = "" + owner: str = "" + verifier: str = "" + correlation_id: str = "" + trace_id: str = "" + created_at: datetime | None = None + updated_at: datetime | None = None + acceptance_criteria: list[str] = Field(default_factory=list) + knowledge_context_refs: list[str] = Field(default_factory=list) + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class VerificationObjectiveSummary(VersionedModel): + """Machine/operator verification objective attached to a case or handoff.""" + + objective_id: str + case_id: str + handoff_id: str = "" + objective_key: str = "" + objective_type: str = "" + name: str + description: str = "" + required_status: str = "pass" + status: str = "pending" + required: bool = True + consecutive_pass_count: int = Field(default=0, ge=0) + required_consecutive_passes: int = Field(default=1, ge=1) + last_checked_at: datetime | None = None + next_check_at: datetime | None = None + evidence_ref: str = "" + failure_reason: str = "" + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class KnowledgeArtifactSummary(VersionedModel): + """Review-gated Knowledge artifact summary attached to a case/handoff.""" + + artifact_id: str + case_id: str + handoff_id: str = "" + artifact_type: str = "" + scope: str = "" + status: str = "proposed" + review_status: str = "pending" + version: int = Field(default=1, ge=1) + content_hash: str = "" + summary: str = "" + source_refs: list[str] = Field(default_factory=list) + created_by: str = "knowledge" + created_at: datetime | None = None + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class OutcomeSummary(VersionedModel): + """Final or interim outcome used by learning/impact analysis.""" + + outcome_id: str + work_item_type: str = "case" + work_item_id: str + case_type: str = "" + proposed_action: str = "" + action_taken: str = "" + agent_roles: list[str] = Field(default_factory=list) + final_score: dict[str, float] = Field(default_factory=dict) + evidence_refs: list[str] = Field(default_factory=list) + created_at: datetime | None = None + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class TopologyNode(VersionedModel): + """Node for loop topology and dependency graph visualizations.""" + + node_id: str + kind: TopologyNodeKind + label: str + status: LoopStatus | ActionStatus = "unknown" + loop_id: str = "" + summary: str = "" + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class TopologyEdge(VersionedModel): + """Directed edge for loop topology and handoff/dependency graphs.""" + + edge_id: str = Field(default_factory=lambda: _observatory_id("edge")) + source_id: str + target_id: str + kind: TopologyEdgeKind = "other" + label: str = "" + status: ActionStatus = "unknown" + summary: str = "" + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class LoopTopology(VersionedModel): + """Complete graph payload with a server-renderable table fallback.""" + + nodes: list[TopologyNode] = Field(default_factory=list) + edges: list[TopologyEdge] = Field(default_factory=list) + generated_at: datetime = Field(default_factory=utcnow) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class ChangeRef(VersionedModel): + """A repository/PR/deploy change key used to join traces, CI, and outcomes.""" + + change_key: str + repository: str = "" + title: str = "" + pr_number: int | None = Field(default=None, ge=1) + issue_number: int | None = Field(default=None, ge=1) + commit_sha: str = "" + workflow_run_id: str = "" + deploy_id: str = "" + merged_at: datetime | None = None + deployed_at: datetime | None = None + links: list[ObservatoryLink] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class ImpactWindow(VersionedModel): + """Time window used by deterministic before/after analysis.""" + + label: Literal["baseline", "observation", "custom"] = "custom" + starts_at: datetime + ends_at: datetime + sample_size: int = Field(default=0, ge=0) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class MetricDelta(VersionedModel): + """One raw metric and its before/after delta for a change analysis.""" + + metric: str + label: str = "" + unit: str = "" + direction: MetricDirection = "neutral" + baseline_value: float | None = None + observed_value: float | None = None + delta: float | None = None + percent_delta: float | None = None + confidence: Confidence = "unknown" + rationale: str = "" + + +class ScoreComponent(VersionedModel): + """Scorecard component: speed, quality, autonomy, cost, or safety.""" + + component: ImpactComponentKind + baseline_score: float | None = Field(default=None, ge=0.0, le=100.0) + observed_score: float | None = Field(default=None, ge=0.0, le=100.0) + delta: float | None = None + weight: float = Field(default=1.0, ge=0.0) + confidence: Confidence = "unknown" + metrics: list[MetricDelta] = Field(default_factory=list) + rationale: str = "" + + +class ChangeImpactReport(VersionedModel): + """Balanced better/worse/mixed/inconclusive report for one change.""" + + report_id: str = Field(default_factory=lambda: _observatory_id("impact")) + change: ChangeRef + verdict: ImpactVerdict = "inconclusive" + baseline_window: ImpactWindow + observation_window: ImpactWindow + baseline_score: float | None = Field(default=None, ge=0.0, le=100.0) + observed_score: float | None = Field(default=None, ge=0.0, le=100.0) + score_delta: float | None = None + safety_regressed: bool = False + components: list[ScoreComponent] = Field(default_factory=list) + evidence: list[ObservatoryLink] = Field(default_factory=list) + narrative: str = "" + generated_at: datetime = Field(default_factory=utcnow) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class ObservatoryActionRequest(VersionedModel): + """Authenticated, CSRF-protected UI intent before source-specific dispatch.""" + + action_id: str = Field(default_factory=lambda: _observatory_id("act")) + action: str + target_type: str + target_id: str + actor: ActorRef + idempotency_key: str + reason: str = "" + payload: dict[str, Any] = Field(default_factory=dict) + requested_at: datetime = Field(default_factory=utcnow) + + +class ObservatoryActionResult(VersionedModel): + """Audited result of an observatory action after source-of-truth validation.""" + + action_id: str + status: ActionStatus + target_type: str = "" + target_id: str = "" + idempotency_key: str = "" + message: str = "" + audit_event_id: str = "" + case_event_id: str = "" + links: list[ObservatoryLink] = Field(default_factory=list) + payload: dict[str, Any] = Field(default_factory=dict) + completed_at: datetime = Field(default_factory=utcnow) + + +class ObservatorySnapshot(VersionedModel): + """Home/dashboard aggregate assembled from loops, cases, runs, and analysis.""" + + loops: list[LoopDescriptor] = Field(default_factory=list) + runtime: list[LoopRuntimeSnapshot] = Field(default_factory=list) + recent_runs: list[RunSummary] = Field(default_factory=list) + recent_cases: list[CaseSummary] = Field(default_factory=list) + pending_handoffs: list[HandoffSummary] = Field(default_factory=list) + recent_analysis: list[ChangeImpactReport] = Field(default_factory=list) + generated_at: datetime = Field(default_factory=utcnow) + metadata: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/schemas/ActorRef.schema.json b/agent_core/contracts/schemas/ActorRef.schema.json new file mode 100644 index 0000000..227ad40 --- /dev/null +++ b/agent_core/contracts/schemas/ActorRef.schema.json @@ -0,0 +1,46 @@ +{ + "additionalProperties": false, + "description": "Human, loop, service, or system actor shown in audit/timeline UI.", + "properties": { + "actor_id": { + "default": "", + "title": "Actor Id", + "type": "string" + }, + "actor_type": { + "default": "unknown", + "enum": [ + "operator", + "loop", + "service", + "system", + "external", + "unknown" + ], + "title": "Actor Type", + "type": "string" + }, + "display_name": { + "default": "", + "title": "Display Name", + "type": "string" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "role": { + "default": "", + "title": "Role", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "ActorRef", + "type": "object" +} diff --git a/agent_core/contracts/schemas/CaseSummary.schema.json b/agent_core/contracts/schemas/CaseSummary.schema.json new file mode 100644 index 0000000..8bf20ea --- /dev/null +++ b/agent_core/contracts/schemas/CaseSummary.schema.json @@ -0,0 +1,200 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "CaseService case projection reduced to the observatory list/detail header.", + "properties": { + "case_id": { + "title": "Case Id", + "type": "string" + }, + "case_number": { + "default": "", + "title": "Case Number", + "type": "string" + }, + "feedback_count": { + "default": 0, + "minimum": 0, + "title": "Feedback Count", + "type": "integer" + }, + "handoff_count": { + "default": 0, + "minimum": 0, + "title": "Handoff Count", + "type": "integer" + }, + "issue_url": { + "default": "", + "title": "Issue Url", + "type": "string" + }, + "kind": { + "default": "unknown", + "enum": [ + "atomic", + "meta", + "unknown" + ], + "title": "Kind", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "opened_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Opened At" + }, + "origin": { + "default": "", + "title": "Origin", + "type": "string" + }, + "resolved_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resolved At" + }, + "resource_id": { + "default": "", + "title": "Resource Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "severity": { + "default": "UNKNOWN", + "title": "Severity", + "type": "string" + }, + "status": { + "default": "", + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "trace_ids": { + "items": { + "type": "string" + }, + "title": "Trace Ids", + "type": "array" + }, + "updated_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Updated At" + }, + "verification_pending_count": { + "default": 0, + "minimum": 0, + "title": "Verification Pending Count", + "type": "integer" + } + }, + "required": [ + "case_id" + ], + "title": "CaseSummary", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ChangeImpactReport.schema.json b/agent_core/contracts/schemas/ChangeImpactReport.schema.json new file mode 100644 index 0000000..c7466ab --- /dev/null +++ b/agent_core/contracts/schemas/ChangeImpactReport.schema.json @@ -0,0 +1,527 @@ +{ + "$defs": { + "ChangeRef": { + "additionalProperties": false, + "description": "A repository/PR/deploy change key used to join traces, CI, and outcomes.", + "properties": { + "change_key": { + "title": "Change Key", + "type": "string" + }, + "commit_sha": { + "default": "", + "title": "Commit Sha", + "type": "string" + }, + "deploy_id": { + "default": "", + "title": "Deploy Id", + "type": "string" + }, + "deployed_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deployed At" + }, + "issue_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Issue Number" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "merged_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Merged At" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "pr_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pr Number" + }, + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "workflow_run_id": { + "default": "", + "title": "Workflow Run Id", + "type": "string" + } + }, + "required": [ + "change_key" + ], + "title": "ChangeRef", + "type": "object" + }, + "ImpactWindow": { + "additionalProperties": false, + "description": "Time window used by deterministic before/after analysis.", + "properties": { + "ends_at": { + "format": "date-time", + "title": "Ends At", + "type": "string" + }, + "label": { + "default": "custom", + "enum": [ + "baseline", + "observation", + "custom" + ], + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "sample_size": { + "default": 0, + "minimum": 0, + "title": "Sample Size", + "type": "integer" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "starts_at": { + "format": "date-time", + "title": "Starts At", + "type": "string" + } + }, + "required": [ + "starts_at", + "ends_at" + ], + "title": "ImpactWindow", + "type": "object" + }, + "MetricDelta": { + "additionalProperties": false, + "description": "One raw metric and its before/after delta for a change analysis.", + "properties": { + "baseline_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Value" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "direction": { + "default": "neutral", + "enum": [ + "higher_is_better", + "lower_is_better", + "neutral" + ], + "title": "Direction", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metric": { + "title": "Metric", + "type": "string" + }, + "observed_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Value" + }, + "percent_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Percent Delta" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "unit": { + "default": "", + "title": "Unit", + "type": "string" + } + }, + "required": [ + "metric" + ], + "title": "MetricDelta", + "type": "object" + }, + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + }, + "ScoreComponent": { + "additionalProperties": false, + "description": "Scorecard component: speed, quality, autonomy, cost, or safety.", + "properties": { + "baseline_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Score" + }, + "component": { + "enum": [ + "speed", + "quality", + "autonomy", + "cost", + "safety" + ], + "title": "Component", + "type": "string" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "metrics": { + "items": { + "$ref": "#/$defs/MetricDelta" + }, + "title": "Metrics", + "type": "array" + }, + "observed_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Score" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "weight": { + "default": 1.0, + "minimum": 0.0, + "title": "Weight", + "type": "number" + } + }, + "required": [ + "component" + ], + "title": "ScoreComponent", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Balanced better/worse/mixed/inconclusive report for one change.", + "properties": { + "baseline_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Score" + }, + "baseline_window": { + "$ref": "#/$defs/ImpactWindow" + }, + "change": { + "$ref": "#/$defs/ChangeRef" + }, + "components": { + "items": { + "$ref": "#/$defs/ScoreComponent" + }, + "title": "Components", + "type": "array" + }, + "evidence": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Evidence", + "type": "array" + }, + "generated_at": { + "format": "date-time", + "title": "Generated At", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "narrative": { + "default": "", + "title": "Narrative", + "type": "string" + }, + "observation_window": { + "$ref": "#/$defs/ImpactWindow" + }, + "observed_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Score" + }, + "report_id": { + "title": "Report Id", + "type": "string" + }, + "safety_regressed": { + "default": false, + "title": "Safety Regressed", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "score_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Score Delta" + }, + "verdict": { + "default": "inconclusive", + "enum": [ + "better", + "worse", + "mixed", + "inconclusive" + ], + "title": "Verdict", + "type": "string" + } + }, + "required": [ + "change", + "baseline_window", + "observation_window" + ], + "title": "ChangeImpactReport", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ChangeRef.schema.json b/agent_core/contracts/schemas/ChangeRef.schema.json new file mode 100644 index 0000000..f4781d2 --- /dev/null +++ b/agent_core/contracts/schemas/ChangeRef.schema.json @@ -0,0 +1,163 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "A repository/PR/deploy change key used to join traces, CI, and outcomes.", + "properties": { + "change_key": { + "title": "Change Key", + "type": "string" + }, + "commit_sha": { + "default": "", + "title": "Commit Sha", + "type": "string" + }, + "deploy_id": { + "default": "", + "title": "Deploy Id", + "type": "string" + }, + "deployed_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deployed At" + }, + "issue_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Issue Number" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "merged_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Merged At" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "pr_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pr Number" + }, + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "workflow_run_id": { + "default": "", + "title": "Workflow Run Id", + "type": "string" + } + }, + "required": [ + "change_key" + ], + "title": "ChangeRef", + "type": "object" +} diff --git a/agent_core/contracts/schemas/HandoffSummary.schema.json b/agent_core/contracts/schemas/HandoffSummary.schema.json new file mode 100644 index 0000000..617149a --- /dev/null +++ b/agent_core/contracts/schemas/HandoffSummary.schema.json @@ -0,0 +1,176 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Loop Handoff Protocol state shown without exposing unbounded payloads.", + "properties": { + "acceptance_criteria": { + "items": { + "type": "string" + }, + "title": "Acceptance Criteria", + "type": "array" + }, + "case_id": { + "title": "Case Id", + "type": "string" + }, + "correlation_id": { + "default": "", + "title": "Correlation Id", + "type": "string" + }, + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Created At" + }, + "handoff_id": { + "title": "Handoff Id", + "type": "string" + }, + "knowledge_context_refs": { + "items": { + "type": "string" + }, + "title": "Knowledge Context Refs", + "type": "array" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "objective": { + "default": "", + "title": "Objective", + "type": "string" + }, + "objective_key": { + "default": "", + "title": "Objective Key", + "type": "string" + }, + "owner": { + "default": "", + "title": "Owner", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source_loop": { + "default": "", + "title": "Source Loop", + "type": "string" + }, + "status": { + "default": "", + "title": "Status", + "type": "string" + }, + "target_loop": { + "default": "", + "title": "Target Loop", + "type": "string" + }, + "trace_id": { + "default": "", + "title": "Trace Id", + "type": "string" + }, + "updated_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Updated At" + }, + "verifier": { + "default": "", + "title": "Verifier", + "type": "string" + } + }, + "required": [ + "handoff_id", + "case_id" + ], + "title": "HandoffSummary", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ImpactWindow.schema.json b/agent_core/contracts/schemas/ImpactWindow.schema.json new file mode 100644 index 0000000..7054bf3 --- /dev/null +++ b/agent_core/contracts/schemas/ImpactWindow.schema.json @@ -0,0 +1,48 @@ +{ + "additionalProperties": false, + "description": "Time window used by deterministic before/after analysis.", + "properties": { + "ends_at": { + "format": "date-time", + "title": "Ends At", + "type": "string" + }, + "label": { + "default": "custom", + "enum": [ + "baseline", + "observation", + "custom" + ], + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "sample_size": { + "default": 0, + "minimum": 0, + "title": "Sample Size", + "type": "integer" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "starts_at": { + "format": "date-time", + "title": "Starts At", + "type": "string" + } + }, + "required": [ + "starts_at", + "ends_at" + ], + "title": "ImpactWindow", + "type": "object" +} diff --git a/agent_core/contracts/schemas/KnowledgeArtifactSummary.schema.json b/agent_core/contracts/schemas/KnowledgeArtifactSummary.schema.json new file mode 100644 index 0000000..b2581fb --- /dev/null +++ b/agent_core/contracts/schemas/KnowledgeArtifactSummary.schema.json @@ -0,0 +1,157 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Review-gated Knowledge artifact summary attached to a case/handoff.", + "properties": { + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "artifact_type": { + "default": "", + "title": "Artifact Type", + "type": "string" + }, + "case_id": { + "title": "Case Id", + "type": "string" + }, + "content_hash": { + "default": "", + "title": "Content Hash", + "type": "string" + }, + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Created At" + }, + "created_by": { + "default": "knowledge", + "title": "Created By", + "type": "string" + }, + "handoff_id": { + "default": "", + "title": "Handoff Id", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "review_status": { + "default": "pending", + "title": "Review Status", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "scope": { + "default": "", + "title": "Scope", + "type": "string" + }, + "source_refs": { + "items": { + "type": "string" + }, + "title": "Source Refs", + "type": "array" + }, + "status": { + "default": "proposed", + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "version": { + "default": 1, + "minimum": 1, + "title": "Version", + "type": "integer" + } + }, + "required": [ + "artifact_id", + "case_id" + ], + "title": "KnowledgeArtifactSummary", + "type": "object" +} diff --git a/agent_core/contracts/schemas/LoopDescriptor.schema.json b/agent_core/contracts/schemas/LoopDescriptor.schema.json new file mode 100644 index 0000000..c3e8fa0 --- /dev/null +++ b/agent_core/contracts/schemas/LoopDescriptor.schema.json @@ -0,0 +1,168 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Static-ish descriptor for one agentic loop or future loop placeholder.", + "properties": { + "capabilities": { + "items": { + "type": "string" + }, + "title": "Capabilities", + "type": "array" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "display_name": { + "title": "Display Name", + "type": "string" + }, + "environment": { + "default": "production", + "title": "Environment", + "type": "string" + }, + "host": { + "default": "", + "title": "Host", + "type": "string" + }, + "input_channels": { + "items": { + "type": "string" + }, + "title": "Input Channels", + "type": "array" + }, + "kind": { + "default": "other", + "enum": [ + "engineering", + "noc", + "knowledge", + "soc", + "collector", + "observability", + "other" + ], + "title": "Kind", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "output_channels": { + "items": { + "type": "string" + }, + "title": "Output Channels", + "type": "array" + }, + "owner": { + "default": "", + "title": "Owner", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "service_name": { + "default": "", + "title": "Service Name", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "trace_graph_id": { + "default": "", + "title": "Trace Graph Id", + "type": "string" + } + }, + "required": [ + "loop_id", + "display_name" + ], + "title": "LoopDescriptor", + "type": "object" +} diff --git a/agent_core/contracts/schemas/LoopRuntimeSnapshot.schema.json b/agent_core/contracts/schemas/LoopRuntimeSnapshot.schema.json new file mode 100644 index 0000000..9f50206 --- /dev/null +++ b/agent_core/contracts/schemas/LoopRuntimeSnapshot.schema.json @@ -0,0 +1,154 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Current operational snapshot for one loop card on the observatory home page.", + "properties": { + "active_case_id": { + "default": "", + "title": "Active Case Id", + "type": "string" + }, + "active_handoff_id": { + "default": "", + "title": "Active Handoff Id", + "type": "string" + }, + "active_run_id": { + "default": "", + "title": "Active Run Id", + "type": "string" + }, + "checked_at": { + "format": "date-time", + "title": "Checked At", + "type": "string" + }, + "failed_action_count": { + "default": 0, + "minimum": 0, + "title": "Failed Action Count", + "type": "integer" + }, + "health": { + "additionalProperties": true, + "title": "Health", + "type": "object" + }, + "last_event_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Event At" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "title": "Loop Id", + "type": "string" + }, + "pending_action_count": { + "default": 0, + "minimum": 0, + "title": "Pending Action Count", + "type": "integer" + }, + "recent_action_count": { + "default": 0, + "minimum": 0, + "title": "Recent Action Count", + "type": "integer" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + } + }, + "required": [ + "loop_id" + ], + "title": "LoopRuntimeSnapshot", + "type": "object" +} diff --git a/agent_core/contracts/schemas/LoopTopology.schema.json b/agent_core/contracts/schemas/LoopTopology.schema.json new file mode 100644 index 0000000..db199ea --- /dev/null +++ b/agent_core/contracts/schemas/LoopTopology.schema.json @@ -0,0 +1,268 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + }, + "TopologyEdge": { + "additionalProperties": false, + "description": "Directed edge for loop topology and handoff/dependency graphs.", + "properties": { + "edge_id": { + "title": "Edge Id", + "type": "string" + }, + "kind": { + "default": "other", + "enum": [ + "emits_trace", + "reads", + "writes", + "requests_handoff", + "updates_handoff", + "verifies", + "opens_pr", + "deploys", + "notifies", + "depends_on", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source_id": { + "title": "Source Id", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "target_id": { + "title": "Target Id", + "type": "string" + } + }, + "required": [ + "source_id", + "target_id" + ], + "title": "TopologyEdge", + "type": "object" + }, + "TopologyNode": { + "additionalProperties": false, + "description": "Node for loop topology and dependency graph visualizations.", + "properties": { + "kind": { + "enum": [ + "loop", + "service", + "collector", + "case", + "handoff", + "repository", + "knowledge_base", + "operator", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "title": "Label", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "title": "Node Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "anyOf": [ + { + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "type": "string" + }, + { + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "type": "string" + } + ], + "default": "unknown", + "title": "Status" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + } + }, + "required": [ + "node_id", + "kind", + "label" + ], + "title": "TopologyNode", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Complete graph payload with a server-renderable table fallback.", + "properties": { + "edges": { + "items": { + "$ref": "#/$defs/TopologyEdge" + }, + "title": "Edges", + "type": "array" + }, + "generated_at": { + "format": "date-time", + "title": "Generated At", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "nodes": { + "items": { + "$ref": "#/$defs/TopologyNode" + }, + "title": "Nodes", + "type": "array" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "LoopTopology", + "type": "object" +} diff --git a/agent_core/contracts/schemas/MetricDelta.schema.json b/agent_core/contracts/schemas/MetricDelta.schema.json new file mode 100644 index 0000000..eee6e54 --- /dev/null +++ b/agent_core/contracts/schemas/MetricDelta.schema.json @@ -0,0 +1,104 @@ +{ + "additionalProperties": false, + "description": "One raw metric and its before/after delta for a change analysis.", + "properties": { + "baseline_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Value" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "direction": { + "default": "neutral", + "enum": [ + "higher_is_better", + "lower_is_better", + "neutral" + ], + "title": "Direction", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metric": { + "title": "Metric", + "type": "string" + }, + "observed_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Value" + }, + "percent_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Percent Delta" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "unit": { + "default": "", + "title": "Unit", + "type": "string" + } + }, + "required": [ + "metric" + ], + "title": "MetricDelta", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ObservatoryActionRequest.schema.json b/agent_core/contracts/schemas/ObservatoryActionRequest.schema.json new file mode 100644 index 0000000..c184041 --- /dev/null +++ b/agent_core/contracts/schemas/ObservatoryActionRequest.schema.json @@ -0,0 +1,106 @@ +{ + "$defs": { + "ActorRef": { + "additionalProperties": false, + "description": "Human, loop, service, or system actor shown in audit/timeline UI.", + "properties": { + "actor_id": { + "default": "", + "title": "Actor Id", + "type": "string" + }, + "actor_type": { + "default": "unknown", + "enum": [ + "operator", + "loop", + "service", + "system", + "external", + "unknown" + ], + "title": "Actor Type", + "type": "string" + }, + "display_name": { + "default": "", + "title": "Display Name", + "type": "string" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "role": { + "default": "", + "title": "Role", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "ActorRef", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Authenticated, CSRF-protected UI intent before source-specific dispatch.", + "properties": { + "action": { + "title": "Action", + "type": "string" + }, + "action_id": { + "title": "Action Id", + "type": "string" + }, + "actor": { + "$ref": "#/$defs/ActorRef" + }, + "idempotency_key": { + "title": "Idempotency Key", + "type": "string" + }, + "payload": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + }, + "reason": { + "default": "", + "title": "Reason", + "type": "string" + }, + "requested_at": { + "format": "date-time", + "title": "Requested At", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "target_id": { + "title": "Target Id", + "type": "string" + }, + "target_type": { + "title": "Target Type", + "type": "string" + } + }, + "required": [ + "action", + "target_type", + "target_id", + "actor", + "idempotency_key" + ], + "title": "ObservatoryActionRequest", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ObservatoryActionResult.schema.json b/agent_core/contracts/schemas/ObservatoryActionResult.schema.json new file mode 100644 index 0000000..925f1be --- /dev/null +++ b/agent_core/contracts/schemas/ObservatoryActionResult.schema.json @@ -0,0 +1,136 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Audited result of an observatory action after source-of-truth validation.", + "properties": { + "action_id": { + "title": "Action Id", + "type": "string" + }, + "audit_event_id": { + "default": "", + "title": "Audit Event Id", + "type": "string" + }, + "case_event_id": { + "default": "", + "title": "Case Event Id", + "type": "string" + }, + "completed_at": { + "format": "date-time", + "title": "Completed At", + "type": "string" + }, + "idempotency_key": { + "default": "", + "title": "Idempotency Key", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "message": { + "default": "", + "title": "Message", + "type": "string" + }, + "payload": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "target_id": { + "default": "", + "title": "Target Id", + "type": "string" + }, + "target_type": { + "default": "", + "title": "Target Type", + "type": "string" + } + }, + "required": [ + "action_id", + "status" + ], + "title": "ObservatoryActionResult", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ObservatoryLink.schema.json b/agent_core/contracts/schemas/ObservatoryLink.schema.json new file mode 100644 index 0000000..9c39f12 --- /dev/null +++ b/agent_core/contracts/schemas/ObservatoryLink.schema.json @@ -0,0 +1,52 @@ +{ + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ObservatorySnapshot.schema.json b/agent_core/contracts/schemas/ObservatorySnapshot.schema.json new file mode 100644 index 0000000..19aba40 --- /dev/null +++ b/agent_core/contracts/schemas/ObservatorySnapshot.schema.json @@ -0,0 +1,1257 @@ +{ + "$defs": { + "CaseSummary": { + "additionalProperties": false, + "description": "CaseService case projection reduced to the observatory list/detail header.", + "properties": { + "case_id": { + "title": "Case Id", + "type": "string" + }, + "case_number": { + "default": "", + "title": "Case Number", + "type": "string" + }, + "feedback_count": { + "default": 0, + "minimum": 0, + "title": "Feedback Count", + "type": "integer" + }, + "handoff_count": { + "default": 0, + "minimum": 0, + "title": "Handoff Count", + "type": "integer" + }, + "issue_url": { + "default": "", + "title": "Issue Url", + "type": "string" + }, + "kind": { + "default": "unknown", + "enum": [ + "atomic", + "meta", + "unknown" + ], + "title": "Kind", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "opened_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Opened At" + }, + "origin": { + "default": "", + "title": "Origin", + "type": "string" + }, + "resolved_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resolved At" + }, + "resource_id": { + "default": "", + "title": "Resource Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "severity": { + "default": "UNKNOWN", + "title": "Severity", + "type": "string" + }, + "status": { + "default": "", + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "trace_ids": { + "items": { + "type": "string" + }, + "title": "Trace Ids", + "type": "array" + }, + "updated_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Updated At" + }, + "verification_pending_count": { + "default": 0, + "minimum": 0, + "title": "Verification Pending Count", + "type": "integer" + } + }, + "required": [ + "case_id" + ], + "title": "CaseSummary", + "type": "object" + }, + "ChangeImpactReport": { + "additionalProperties": false, + "description": "Balanced better/worse/mixed/inconclusive report for one change.", + "properties": { + "baseline_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Score" + }, + "baseline_window": { + "$ref": "#/$defs/ImpactWindow" + }, + "change": { + "$ref": "#/$defs/ChangeRef" + }, + "components": { + "items": { + "$ref": "#/$defs/ScoreComponent" + }, + "title": "Components", + "type": "array" + }, + "evidence": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Evidence", + "type": "array" + }, + "generated_at": { + "format": "date-time", + "title": "Generated At", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "narrative": { + "default": "", + "title": "Narrative", + "type": "string" + }, + "observation_window": { + "$ref": "#/$defs/ImpactWindow" + }, + "observed_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Score" + }, + "report_id": { + "title": "Report Id", + "type": "string" + }, + "safety_regressed": { + "default": false, + "title": "Safety Regressed", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "score_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Score Delta" + }, + "verdict": { + "default": "inconclusive", + "enum": [ + "better", + "worse", + "mixed", + "inconclusive" + ], + "title": "Verdict", + "type": "string" + } + }, + "required": [ + "change", + "baseline_window", + "observation_window" + ], + "title": "ChangeImpactReport", + "type": "object" + }, + "ChangeRef": { + "additionalProperties": false, + "description": "A repository/PR/deploy change key used to join traces, CI, and outcomes.", + "properties": { + "change_key": { + "title": "Change Key", + "type": "string" + }, + "commit_sha": { + "default": "", + "title": "Commit Sha", + "type": "string" + }, + "deploy_id": { + "default": "", + "title": "Deploy Id", + "type": "string" + }, + "deployed_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deployed At" + }, + "issue_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Issue Number" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "merged_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Merged At" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "pr_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pr Number" + }, + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "workflow_run_id": { + "default": "", + "title": "Workflow Run Id", + "type": "string" + } + }, + "required": [ + "change_key" + ], + "title": "ChangeRef", + "type": "object" + }, + "HandoffSummary": { + "additionalProperties": false, + "description": "Loop Handoff Protocol state shown without exposing unbounded payloads.", + "properties": { + "acceptance_criteria": { + "items": { + "type": "string" + }, + "title": "Acceptance Criteria", + "type": "array" + }, + "case_id": { + "title": "Case Id", + "type": "string" + }, + "correlation_id": { + "default": "", + "title": "Correlation Id", + "type": "string" + }, + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Created At" + }, + "handoff_id": { + "title": "Handoff Id", + "type": "string" + }, + "knowledge_context_refs": { + "items": { + "type": "string" + }, + "title": "Knowledge Context Refs", + "type": "array" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "objective": { + "default": "", + "title": "Objective", + "type": "string" + }, + "objective_key": { + "default": "", + "title": "Objective Key", + "type": "string" + }, + "owner": { + "default": "", + "title": "Owner", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source_loop": { + "default": "", + "title": "Source Loop", + "type": "string" + }, + "status": { + "default": "", + "title": "Status", + "type": "string" + }, + "target_loop": { + "default": "", + "title": "Target Loop", + "type": "string" + }, + "trace_id": { + "default": "", + "title": "Trace Id", + "type": "string" + }, + "updated_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Updated At" + }, + "verifier": { + "default": "", + "title": "Verifier", + "type": "string" + } + }, + "required": [ + "handoff_id", + "case_id" + ], + "title": "HandoffSummary", + "type": "object" + }, + "ImpactWindow": { + "additionalProperties": false, + "description": "Time window used by deterministic before/after analysis.", + "properties": { + "ends_at": { + "format": "date-time", + "title": "Ends At", + "type": "string" + }, + "label": { + "default": "custom", + "enum": [ + "baseline", + "observation", + "custom" + ], + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "sample_size": { + "default": 0, + "minimum": 0, + "title": "Sample Size", + "type": "integer" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "starts_at": { + "format": "date-time", + "title": "Starts At", + "type": "string" + } + }, + "required": [ + "starts_at", + "ends_at" + ], + "title": "ImpactWindow", + "type": "object" + }, + "LoopDescriptor": { + "additionalProperties": false, + "description": "Static-ish descriptor for one agentic loop or future loop placeholder.", + "properties": { + "capabilities": { + "items": { + "type": "string" + }, + "title": "Capabilities", + "type": "array" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "display_name": { + "title": "Display Name", + "type": "string" + }, + "environment": { + "default": "production", + "title": "Environment", + "type": "string" + }, + "host": { + "default": "", + "title": "Host", + "type": "string" + }, + "input_channels": { + "items": { + "type": "string" + }, + "title": "Input Channels", + "type": "array" + }, + "kind": { + "default": "other", + "enum": [ + "engineering", + "noc", + "knowledge", + "soc", + "collector", + "observability", + "other" + ], + "title": "Kind", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "output_channels": { + "items": { + "type": "string" + }, + "title": "Output Channels", + "type": "array" + }, + "owner": { + "default": "", + "title": "Owner", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "service_name": { + "default": "", + "title": "Service Name", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "trace_graph_id": { + "default": "", + "title": "Trace Graph Id", + "type": "string" + } + }, + "required": [ + "loop_id", + "display_name" + ], + "title": "LoopDescriptor", + "type": "object" + }, + "LoopRuntimeSnapshot": { + "additionalProperties": false, + "description": "Current operational snapshot for one loop card on the observatory home page.", + "properties": { + "active_case_id": { + "default": "", + "title": "Active Case Id", + "type": "string" + }, + "active_handoff_id": { + "default": "", + "title": "Active Handoff Id", + "type": "string" + }, + "active_run_id": { + "default": "", + "title": "Active Run Id", + "type": "string" + }, + "checked_at": { + "format": "date-time", + "title": "Checked At", + "type": "string" + }, + "failed_action_count": { + "default": 0, + "minimum": 0, + "title": "Failed Action Count", + "type": "integer" + }, + "health": { + "additionalProperties": true, + "title": "Health", + "type": "object" + }, + "last_event_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Event At" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "title": "Loop Id", + "type": "string" + }, + "pending_action_count": { + "default": 0, + "minimum": 0, + "title": "Pending Action Count", + "type": "integer" + }, + "recent_action_count": { + "default": 0, + "minimum": 0, + "title": "Recent Action Count", + "type": "integer" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + } + }, + "required": [ + "loop_id" + ], + "title": "LoopRuntimeSnapshot", + "type": "object" + }, + "MetricDelta": { + "additionalProperties": false, + "description": "One raw metric and its before/after delta for a change analysis.", + "properties": { + "baseline_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Value" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "direction": { + "default": "neutral", + "enum": [ + "higher_is_better", + "lower_is_better", + "neutral" + ], + "title": "Direction", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metric": { + "title": "Metric", + "type": "string" + }, + "observed_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Value" + }, + "percent_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Percent Delta" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "unit": { + "default": "", + "title": "Unit", + "type": "string" + } + }, + "required": [ + "metric" + ], + "title": "MetricDelta", + "type": "object" + }, + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + }, + "RunSummary": { + "additionalProperties": false, + "description": "Trace-backed run/cycle summary assembled from collector rows.", + "properties": { + "case_ids": { + "items": { + "type": "string" + }, + "title": "Case Ids", + "type": "array" + }, + "change_ids": { + "items": { + "type": "string" + }, + "title": "Change Ids", + "type": "array" + }, + "cost_usd": { + "anyOf": [ + { + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cost Usd" + }, + "ended_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ended At" + }, + "error_count": { + "default": 0, + "minimum": 0, + "title": "Error Count", + "type": "integer" + }, + "event_count": { + "default": 0, + "minimum": 0, + "title": "Event Count", + "type": "integer" + }, + "graph_id": { + "default": "", + "title": "Graph Id", + "type": "string" + }, + "handoff_ids": { + "items": { + "type": "string" + }, + "title": "Handoff Ids", + "type": "array" + }, + "input_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "last_event_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Event At" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "output_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "started_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Started At" + }, + "status": { + "default": "unknown", + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "trace_id": { + "default": "", + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "run_id" + ], + "title": "RunSummary", + "type": "object" + }, + "ScoreComponent": { + "additionalProperties": false, + "description": "Scorecard component: speed, quality, autonomy, cost, or safety.", + "properties": { + "baseline_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Score" + }, + "component": { + "enum": [ + "speed", + "quality", + "autonomy", + "cost", + "safety" + ], + "title": "Component", + "type": "string" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "metrics": { + "items": { + "$ref": "#/$defs/MetricDelta" + }, + "title": "Metrics", + "type": "array" + }, + "observed_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Score" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "weight": { + "default": 1.0, + "minimum": 0.0, + "title": "Weight", + "type": "number" + } + }, + "required": [ + "component" + ], + "title": "ScoreComponent", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Home/dashboard aggregate assembled from loops, cases, runs, and analysis.", + "properties": { + "generated_at": { + "format": "date-time", + "title": "Generated At", + "type": "string" + }, + "loops": { + "items": { + "$ref": "#/$defs/LoopDescriptor" + }, + "title": "Loops", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "pending_handoffs": { + "items": { + "$ref": "#/$defs/HandoffSummary" + }, + "title": "Pending Handoffs", + "type": "array" + }, + "recent_analysis": { + "items": { + "$ref": "#/$defs/ChangeImpactReport" + }, + "title": "Recent Analysis", + "type": "array" + }, + "recent_cases": { + "items": { + "$ref": "#/$defs/CaseSummary" + }, + "title": "Recent Cases", + "type": "array" + }, + "recent_runs": { + "items": { + "$ref": "#/$defs/RunSummary" + }, + "title": "Recent Runs", + "type": "array" + }, + "runtime": { + "items": { + "$ref": "#/$defs/LoopRuntimeSnapshot" + }, + "title": "Runtime", + "type": "array" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "ObservatorySnapshot", + "type": "object" +} diff --git a/agent_core/contracts/schemas/OutcomeSummary.schema.json b/agent_core/contracts/schemas/OutcomeSummary.schema.json new file mode 100644 index 0000000..31848e8 --- /dev/null +++ b/agent_core/contracts/schemas/OutcomeSummary.schema.json @@ -0,0 +1,145 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Final or interim outcome used by learning/impact analysis.", + "properties": { + "action_taken": { + "default": "", + "title": "Action Taken", + "type": "string" + }, + "agent_roles": { + "items": { + "type": "string" + }, + "title": "Agent Roles", + "type": "array" + }, + "case_type": { + "default": "", + "title": "Case Type", + "type": "string" + }, + "created_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Created At" + }, + "evidence_refs": { + "items": { + "type": "string" + }, + "title": "Evidence Refs", + "type": "array" + }, + "final_score": { + "additionalProperties": { + "type": "number" + }, + "title": "Final Score", + "type": "object" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "outcome_id": { + "title": "Outcome Id", + "type": "string" + }, + "proposed_action": { + "default": "", + "title": "Proposed Action", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "work_item_id": { + "title": "Work Item Id", + "type": "string" + }, + "work_item_type": { + "default": "case", + "title": "Work Item Type", + "type": "string" + } + }, + "required": [ + "outcome_id", + "work_item_id" + ], + "title": "OutcomeSummary", + "type": "object" +} diff --git a/agent_core/contracts/schemas/RunSummary.schema.json b/agent_core/contracts/schemas/RunSummary.schema.json new file mode 100644 index 0000000..0194ecf --- /dev/null +++ b/agent_core/contracts/schemas/RunSummary.schema.json @@ -0,0 +1,237 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Trace-backed run/cycle summary assembled from collector rows.", + "properties": { + "case_ids": { + "items": { + "type": "string" + }, + "title": "Case Ids", + "type": "array" + }, + "change_ids": { + "items": { + "type": "string" + }, + "title": "Change Ids", + "type": "array" + }, + "cost_usd": { + "anyOf": [ + { + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Cost Usd" + }, + "ended_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ended At" + }, + "error_count": { + "default": 0, + "minimum": 0, + "title": "Error Count", + "type": "integer" + }, + "event_count": { + "default": 0, + "minimum": 0, + "title": "Event Count", + "type": "integer" + }, + "graph_id": { + "default": "", + "title": "Graph Id", + "type": "string" + }, + "handoff_ids": { + "items": { + "type": "string" + }, + "title": "Handoff Ids", + "type": "array" + }, + "input_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "last_event_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Event At" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "output_tokens": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "started_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Started At" + }, + "status": { + "default": "unknown", + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "title": { + "default": "", + "title": "Title", + "type": "string" + }, + "trace_id": { + "default": "", + "title": "Trace Id", + "type": "string" + } + }, + "required": [ + "run_id" + ], + "title": "RunSummary", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ScoreComponent.schema.json b/agent_core/contracts/schemas/ScoreComponent.schema.json new file mode 100644 index 0000000..79736e4 --- /dev/null +++ b/agent_core/contracts/schemas/ScoreComponent.schema.json @@ -0,0 +1,202 @@ +{ + "$defs": { + "MetricDelta": { + "additionalProperties": false, + "description": "One raw metric and its before/after delta for a change analysis.", + "properties": { + "baseline_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Value" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "direction": { + "default": "neutral", + "enum": [ + "higher_is_better", + "lower_is_better", + "neutral" + ], + "title": "Direction", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metric": { + "title": "Metric", + "type": "string" + }, + "observed_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Value" + }, + "percent_delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Percent Delta" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "unit": { + "default": "", + "title": "Unit", + "type": "string" + } + }, + "required": [ + "metric" + ], + "title": "MetricDelta", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Scorecard component: speed, quality, autonomy, cost, or safety.", + "properties": { + "baseline_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Score" + }, + "component": { + "enum": [ + "speed", + "quality", + "autonomy", + "cost", + "safety" + ], + "title": "Component", + "type": "string" + }, + "confidence": { + "default": "unknown", + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "title": "Confidence", + "type": "string" + }, + "delta": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Delta" + }, + "metrics": { + "items": { + "$ref": "#/$defs/MetricDelta" + }, + "title": "Metrics", + "type": "array" + }, + "observed_score": { + "anyOf": [ + { + "maximum": 100.0, + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Observed Score" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "weight": { + "default": 1.0, + "minimum": 0.0, + "title": "Weight", + "type": "number" + } + }, + "required": [ + "component" + ], + "title": "ScoreComponent", + "type": "object" +} diff --git a/agent_core/contracts/schemas/TimelineItem.schema.json b/agent_core/contracts/schemas/TimelineItem.schema.json new file mode 100644 index 0000000..ce06057 --- /dev/null +++ b/agent_core/contracts/schemas/TimelineItem.schema.json @@ -0,0 +1,255 @@ +{ + "$defs": { + "ActorRef": { + "additionalProperties": false, + "description": "Human, loop, service, or system actor shown in audit/timeline UI.", + "properties": { + "actor_id": { + "default": "", + "title": "Actor Id", + "type": "string" + }, + "actor_type": { + "default": "unknown", + "enum": [ + "operator", + "loop", + "service", + "system", + "external", + "unknown" + ], + "title": "Actor Type", + "type": "string" + }, + "display_name": { + "default": "", + "title": "Display Name", + "type": "string" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "role": { + "default": "", + "title": "Role", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "ActorRef", + "type": "object" + }, + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "A safe, ordered event for server-rendered timelines and replay islands.", + "properties": { + "actor": { + "anyOf": [ + { + "$ref": "#/$defs/ActorRef" + }, + { + "type": "null" + } + ], + "default": null + }, + "case_id": { + "default": "", + "title": "Case Id", + "type": "string" + }, + "change_id": { + "default": "", + "title": "Change Id", + "type": "string" + }, + "handoff_id": { + "default": "", + "title": "Handoff Id", + "type": "string" + }, + "item_id": { + "title": "Item Id", + "type": "string" + }, + "item_type": { + "enum": [ + "trace_event", + "case_event", + "handoff", + "verification", + "knowledge_artifact", + "outcome", + "approval", + "pr", + "ci", + "deploy", + "operator_feedback", + "analysis", + "other" + ], + "title": "Item Type", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "objective_id": { + "default": "", + "title": "Objective Id", + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "title": "Occurred At", + "type": "string" + }, + "parent_item_id": { + "default": "", + "title": "Parent Item Id", + "type": "string" + }, + "payload": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + }, + "repository": { + "default": "", + "title": "Repository", + "type": "string" + }, + "run_id": { + "default": "", + "title": "Run Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "severity": { + "default": "unknown", + "enum": [ + "critical", + "high", + "medium", + "low", + "info", + "unknown" + ], + "title": "Severity", + "type": "string" + }, + "source_loop": { + "default": "", + "title": "Source Loop", + "type": "string" + }, + "source_system": { + "default": "", + "title": "Source System", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "title": { + "title": "Title", + "type": "string" + }, + "trace_event_id": { + "default": "", + "title": "Trace Event Id", + "type": "string" + } + }, + "required": [ + "item_type", + "title" + ], + "title": "TimelineItem", + "type": "object" +} diff --git a/agent_core/contracts/schemas/TopologyEdge.schema.json b/agent_core/contracts/schemas/TopologyEdge.schema.json new file mode 100644 index 0000000..e17556e --- /dev/null +++ b/agent_core/contracts/schemas/TopologyEdge.schema.json @@ -0,0 +1,138 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Directed edge for loop topology and handoff/dependency graphs.", + "properties": { + "edge_id": { + "title": "Edge Id", + "type": "string" + }, + "kind": { + "default": "other", + "enum": [ + "emits_trace", + "reads", + "writes", + "requests_handoff", + "updates_handoff", + "verifies", + "opens_pr", + "deploys", + "notifies", + "depends_on", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source_id": { + "title": "Source Id", + "type": "string" + }, + "status": { + "default": "unknown", + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "title": "Status", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "target_id": { + "title": "Target Id", + "type": "string" + } + }, + "required": [ + "source_id", + "target_id" + ], + "title": "TopologyEdge", + "type": "object" +} diff --git a/agent_core/contracts/schemas/TopologyNode.schema.json b/agent_core/contracts/schemas/TopologyNode.schema.json new file mode 100644 index 0000000..6ff7454 --- /dev/null +++ b/agent_core/contracts/schemas/TopologyNode.schema.json @@ -0,0 +1,147 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Node for loop topology and dependency graph visualizations.", + "properties": { + "kind": { + "enum": [ + "loop", + "service", + "collector", + "case", + "handoff", + "repository", + "knowledge_base", + "operator", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "title": "Label", + "type": "string" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "loop_id": { + "default": "", + "title": "Loop Id", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "title": "Node Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "anyOf": [ + { + "enum": [ + "active", + "idle", + "degraded", + "disabled", + "unknown" + ], + "type": "string" + }, + { + "enum": [ + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown" + ], + "type": "string" + } + ], + "default": "unknown", + "title": "Status" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + } + }, + "required": [ + "node_id", + "kind", + "label" + ], + "title": "TopologyNode", + "type": "object" +} diff --git a/agent_core/contracts/schemas/VerificationObjectiveSummary.schema.json b/agent_core/contracts/schemas/VerificationObjectiveSummary.schema.json new file mode 100644 index 0000000..cbd9d17 --- /dev/null +++ b/agent_core/contracts/schemas/VerificationObjectiveSummary.schema.json @@ -0,0 +1,179 @@ +{ + "$defs": { + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Machine/operator verification objective attached to a case or handoff.", + "properties": { + "case_id": { + "title": "Case Id", + "type": "string" + }, + "consecutive_pass_count": { + "default": 0, + "minimum": 0, + "title": "Consecutive Pass Count", + "type": "integer" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "evidence_ref": { + "default": "", + "title": "Evidence Ref", + "type": "string" + }, + "failure_reason": { + "default": "", + "title": "Failure Reason", + "type": "string" + }, + "handoff_id": { + "default": "", + "title": "Handoff Id", + "type": "string" + }, + "last_checked_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Checked At" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "name": { + "title": "Name", + "type": "string" + }, + "next_check_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Next Check At" + }, + "objective_id": { + "title": "Objective Id", + "type": "string" + }, + "objective_key": { + "default": "", + "title": "Objective Key", + "type": "string" + }, + "objective_type": { + "default": "", + "title": "Objective Type", + "type": "string" + }, + "required": { + "default": true, + "title": "Required", + "type": "boolean" + }, + "required_consecutive_passes": { + "default": 1, + "minimum": 1, + "title": "Required Consecutive Passes", + "type": "integer" + }, + "required_status": { + "default": "pass", + "title": "Required Status", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "status": { + "default": "pending", + "title": "Status", + "type": "string" + } + }, + "required": [ + "objective_id", + "case_id", + "name" + ], + "title": "VerificationObjectiveSummary", + "type": "object" +} diff --git a/tests/contracts/test_observatory_contracts.py b/tests/contracts/test_observatory_contracts.py new file mode 100644 index 0000000..a0c73b7 --- /dev/null +++ b/tests/contracts/test_observatory_contracts.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest +from pydantic import ValidationError + +from agent_core.contracts import ( + ActorRef, + CaseSummary, + ChangeImpactReport, + ChangeRef, + HandoffSummary, + ImpactWindow, + KnowledgeArtifactSummary, + LoopDescriptor, + LoopRuntimeSnapshot, + LoopTopology, + MetricDelta, + ObservatoryActionRequest, + ObservatoryActionResult, + ObservatoryLink, + ObservatorySnapshot, + OutcomeSummary, + RunSummary, + ScoreComponent, + TimelineItem, + TopologyEdge, + TopologyNode, + VerificationObjectiveSummary, +) + + +def _now() -> datetime: + return datetime(2026, 6, 29, tzinfo=UTC) + + +def test_observatory_operational_dtos_roundtrip() -> None: + link = ObservatoryLink(kind="trace", label="trace run", ref_id="run_1") + actor = ActorRef(actor_id="operator", actor_type="operator", display_name="Operator") + loop = LoopDescriptor( + loop_id="engineering", + display_name="Engineering Loop", + kind="engineering", + status="active", + trace_graph_id="engineering-loop", + links=[link], + ) + runtime = LoopRuntimeSnapshot( + loop_id="engineering", + status="active", + active_run_id="run_1", + recent_action_count=3, + ) + run = RunSummary( + run_id="run_1", + loop_id="engineering", + graph_id="engineering-loop", + status="succeeded", + event_count=3, + links=[link], + ) + item = TimelineItem( + item_type="trace_event", + title="gate passed", + status="succeeded", + occurred_at=_now(), + source_loop="engineering", + actor=actor, + run_id="run_1", + trace_event_id="evt_1", + links=[link], + ) + snapshot = ObservatorySnapshot( + loops=[loop], + runtime=[runtime], + recent_runs=[run], + recent_cases=[CaseSummary(case_id="case_1", status="open")], + ) + + for model in (link, actor, loop, runtime, run, item, snapshot): + restored = type(model).model_validate_json(model.model_dump_json()) + assert restored == model + + +@pytest.mark.parametrize( + "model", + [ + HandoffSummary(handoff_id="handoff_1", case_id="case_1", target_loop="engineering"), + VerificationObjectiveSummary( + objective_id="vo_1", + case_id="case_1", + name="three clean checks", + ), + KnowledgeArtifactSummary(artifact_id="ka_1", case_id="case_1", artifact_type="lesson"), + OutcomeSummary(outcome_id="outcome_1", work_item_id="case_1"), + LoopTopology( + nodes=[TopologyNode(node_id="engineering", kind="loop", label="Engineering")], + edges=[TopologyEdge(source_id="noc", target_id="engineering", kind="requests_handoff")], + ), + ], +) +def test_observatory_case_handoff_and_topology_models_roundtrip(model: object) -> None: + restored = type(model).model_validate_json(model.model_dump_json()) + assert restored == model + + +def test_change_impact_report_contract() -> None: + report = ChangeImpactReport( + change=ChangeRef( + change_key="AS215932/network-operations#318", + repository="AS215932/network-operations", + ), + verdict="better", + baseline_window=ImpactWindow( + label="baseline", + starts_at=_now(), + ends_at=_now(), + sample_size=7, + ), + observation_window=ImpactWindow( + label="observation", + starts_at=_now(), + ends_at=_now(), + sample_size=4, + ), + baseline_score=74.0, + observed_score=82.0, + score_delta=8.0, + components=[ + ScoreComponent( + component="quality", + baseline_score=70.0, + observed_score=86.0, + delta=16.0, + confidence="medium", + metrics=[ + MetricDelta( + metric="verification_pass_rate", + direction="higher_is_better", + baseline_value=0.8, + observed_value=1.0, + delta=0.2, + ) + ], + ) + ], + ) + + assert report.verdict == "better" + assert report.components[0].component == "quality" + assert ChangeImpactReport.model_validate_json(report.model_dump_json()) == report + + +def test_observatory_action_contracts_require_valid_status_and_idempotency() -> None: + request = ObservatoryActionRequest( + action="ack", + target_type="case", + target_id="case_1", + actor=ActorRef(actor_id="op", actor_type="operator"), + idempotency_key="ack:case_1:op", + ) + result = ObservatoryActionResult( + action_id=request.action_id, + status="succeeded", + target_type=request.target_type, + target_id=request.target_id, + idempotency_key=request.idempotency_key, + ) + + assert result.action_id == request.action_id + with pytest.raises(ValidationError): + ObservatoryActionResult(action_id="act_1", status="not-a-real-status") From d4c417087208d32439acb60b1a179376c560b419 Mon Sep 17 00:00:00 2001 From: David Hyrule Date: Mon, 29 Jun 2026 21:20:03 +0200 Subject: [PATCH 2/2] feat: add observatory trace query APIs --- agent_core/collector/app.py | 384 +++++++++++++++++- .../contracts/schemas/TraceEvent.schema.json | 168 ++++++++ agent_core/contracts/tracing.py | 15 + tests/collector/test_collector.py | 72 ++++ 4 files changed, 637 insertions(+), 2 deletions(-) diff --git a/agent_core/collector/app.py b/agent_core/collector/app.py index 15d1c60..fbfdcf9 100644 --- a/agent_core/collector/app.py +++ b/agent_core/collector/app.py @@ -8,6 +8,7 @@ from collections.abc import AsyncIterator from contextlib import asynccontextmanager +from datetime import datetime from importlib.metadata import PackageNotFoundError, version from typing import Any @@ -21,6 +22,16 @@ make_sessionmaker, ) from agent_core.contracts._base import utcnow +from agent_core.contracts.observatory import ( + ActorRef, + LoopRuntimeSnapshot, + LoopTopology, + ObservatoryLink, + RunSummary, + TimelineItem, + TopologyEdge, + TopologyNode, +) from agent_core.contracts.tracing import TraceEvent @@ -57,6 +68,189 @@ def _row_from_event(event: TraceEvent) -> TraceEventRow: ) +_ACTION_STATUSES = { + "pending", + "in_progress", + "succeeded", + "failed", + "blocked", + "skipped", + "cancelled", + "unknown", +} +_STATUS_ALIASES = { + "pass": "succeeded", + "passed": "succeeded", + "success": "succeeded", + "successful": "succeeded", + "ok": "succeeded", + "error": "failed", + "failure": "failed", +} +_SEVERITIES = {"critical", "high", "medium", "low", "info", "unknown"} + + +def _limit(value: int, *, default: int = 50, maximum: int = 500) -> int: + if value <= 0: + return default + return min(value, maximum) + + +def _event(row: TraceEventRow) -> dict[str, Any]: + return row.event if isinstance(row.event, dict) else {} + + +def _text(value: Any) -> str: + if value is None: + return "" + return str(value) + + +def _event_text(row: TraceEventRow, key: str) -> str: + return _text(_event(row).get(key)) + + +def _event_links(row: TraceEventRow) -> list[ObservatoryLink]: + raw_links = _event(row).get("links", []) + if not isinstance(raw_links, list): + return [] + links: list[ObservatoryLink] = [] + for raw in raw_links[:20]: + if not isinstance(raw, dict): + continue + try: + links.append(ObservatoryLink.model_validate(raw)) + except Exception: + continue + return links + + +def _status_from_row(row: TraceEventRow) -> str: + event = _event(row) + raw_payload = event.get("payload") + payload: dict[str, Any] = raw_payload if isinstance(raw_payload, dict) else {} + raw = _text(payload.get("status") or event.get("status") or row.event_type).strip().lower() + normalized = _STATUS_ALIASES.get(raw, raw) + if normalized in _ACTION_STATUSES: + return normalized + if "fail" in raw or "error" in raw: + return "failed" + return "unknown" + + +def _severity_from_row(row: TraceEventRow) -> str: + event = _event(row) + raw_payload = event.get("payload") + payload: dict[str, Any] = raw_payload if isinstance(raw_payload, dict) else {} + raw = _text(payload.get("severity") or event.get("severity")).strip().lower() + return raw if raw in _SEVERITIES else "unknown" + + +def _occurred_at(row: TraceEventRow) -> datetime: + return row.timestamp or row.received_at or utcnow() + + +def _timeline_item_from_row(row: TraceEventRow) -> TimelineItem: + actor = None + if row.agent_role: + actor = ActorRef( + actor_id=row.agent_role, + actor_type="loop", + role=row.agent_role, + loop_id=row.graph_id or "", + ) + return TimelineItem( + item_id=row.event_id or f"trace_row_{row.id}", + item_type="trace_event", + title=row.event_type, + summary=row.summary or "", + status=_status_from_row(row), + severity=_severity_from_row(row), + occurred_at=_occurred_at(row), + source_loop=row.graph_id or _event_text(row, "graph_id"), + source_system="agent-core-collector", + actor=actor, + parent_item_id=_event_text(row, "parent_event_id"), + run_id=row.run_id or _event_text(row, "run_id"), + trace_event_id=row.event_id, + case_id=_event_text(row, "case_id"), + handoff_id=_event_text(row, "handoff_id"), + objective_id=_event_text(row, "objective_id"), + change_id=_event_text(row, "change_id"), + repository=_event_text(row, "repository"), + links=_event_links(row), + payload=_event(row), + ) + + +def _run_id(row: TraceEventRow) -> str: + return row.run_id or _event_text(row, "run_id") + + +def _latest_time(rows: list[TraceEventRow]) -> Any: + if not rows: + return None + return max(_occurred_at(row) for row in rows) + + +def _run_summary(run_id: str, rows: list[TraceEventRow]) -> RunSummary: + ordered = sorted(rows, key=_occurred_at) + graph_id = next((row.graph_id for row in ordered if row.graph_id), "") + statuses = {_status_from_row(row) for row in ordered} + status = "failed" if "failed" in statuses else "succeeded" + cost_values = [row.cost_usd for row in ordered if row.cost_usd is not None] + input_values = [row.input_tokens for row in ordered if row.input_tokens is not None] + output_values = [row.output_tokens for row in ordered if row.output_tokens is not None] + case_ids = sorted( + {_event_text(row, "case_id") for row in ordered if _event_text(row, "case_id")} + ) + handoff_ids = sorted( + {_event_text(row, "handoff_id") for row in ordered if _event_text(row, "handoff_id")} + ) + change_ids = sorted( + {_event_text(row, "change_id") for row in ordered if _event_text(row, "change_id")} + ) + return RunSummary( + run_id=run_id, + loop_id=graph_id, + graph_id=graph_id, + trace_id=next((row.trace_id for row in ordered if row.trace_id), ""), + status=status if ordered else "unknown", + title=f"{graph_id or 'trace'} run {run_id}", + summary=f"{len(ordered)} trace event(s)", + started_at=_occurred_at(ordered[0]) if ordered else None, + ended_at=_occurred_at(ordered[-1]) if ordered else None, + last_event_at=_latest_time(ordered), + event_count=len(ordered), + error_count=sum(1 for row in ordered if _status_from_row(row) == "failed"), + cost_usd=sum(cost_values) if cost_values else None, + input_tokens=sum(input_values) if input_values else None, + output_tokens=sum(output_values) if output_values else None, + case_ids=case_ids, + handoff_ids=handoff_ids, + change_ids=change_ids, + ) + + +def _action_matches( + row: TraceEventRow, + *, + graph_id: str | None, + case_id: str | None, + handoff_id: str | None, + change_id: str | None, +) -> bool: + if graph_id and row.graph_id != graph_id: + return False + if case_id and _event_text(row, "case_id") != case_id: + return False + if handoff_id and _event_text(row, "handoff_id") != handoff_id: + return False + if change_id and _event_text(row, "change_id") != change_id: + return False + return True + + def create_app(database_url: str | None = None) -> FastAPI: engine = make_engine(database_url) sessionmaker = make_sessionmaker(engine) @@ -95,11 +289,197 @@ async def ingest_batch(events: list[TraceEvent]) -> dict[str, int]: @app.get("/v1/trace") async def recent(run_id: str | None = None, limit: int = 50) -> list[dict[str, Any]]: - stmt = select(TraceEventRow).order_by(TraceEventRow.id.desc()).limit(min(limit, 500)) + stmt = select(TraceEventRow).order_by(TraceEventRow.id.desc()).limit(_limit(limit)) if run_id: stmt = stmt.where(TraceEventRow.run_id == run_id) async with sessionmaker() as session: - rows = (await session.execute(stmt)).scalars().all() + rows = list((await session.execute(stmt)).scalars().all()) return [row.event for row in rows] + @app.get("/v1/loops") + async def loops(limit: int = 2000) -> list[dict[str, Any]]: + stmt = ( + select(TraceEventRow) + .order_by(TraceEventRow.id.desc()) + .limit(_limit(limit, maximum=5000)) + ) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + grouped: dict[str, list[TraceEventRow]] = {} + for row in rows: + loop_id = row.graph_id or _event_text(row, "graph_id") or "unknown" + grouped.setdefault(loop_id, []).append(row) + snapshots = [] + for loop_id, loop_rows in grouped.items(): + latest = max(loop_rows, key=_occurred_at) + failed_count = sum(1 for row in loop_rows if _status_from_row(row) == "failed") + pending_count = sum( + 1 for row in loop_rows if _status_from_row(row) in {"pending", "in_progress"} + ) + snapshots.append( + LoopRuntimeSnapshot( + loop_id=loop_id, + status="active", + summary=f"{len(loop_rows)} recent trace event(s)", + active_run_id=_run_id(latest), + active_case_id=_event_text(latest, "case_id"), + active_handoff_id=_event_text(latest, "handoff_id"), + recent_action_count=len(loop_rows), + pending_action_count=pending_count, + failed_action_count=failed_count, + last_event_at=_latest_time(loop_rows), + ) + ) + snapshots.sort(key=lambda item: item.last_event_at or utcnow(), reverse=True) + return [item.model_dump(mode="json") for item in snapshots] + + @app.get("/v1/runs") + async def runs(limit: int = 50) -> list[dict[str, Any]]: + row_limit = _limit(limit, maximum=500) * 50 + stmt = select(TraceEventRow).order_by(TraceEventRow.id.desc()).limit(min(row_limit, 5000)) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + grouped: dict[str, list[TraceEventRow]] = {} + for row in rows: + run = _run_id(row) + if run: + grouped.setdefault(run, []).append(row) + summaries = [_run_summary(run, run_rows) for run, run_rows in grouped.items()] + summaries.sort(key=lambda item: item.last_event_at or utcnow(), reverse=True) + return [item.model_dump(mode="json") for item in summaries[: _limit(limit)]] + + @app.get("/v1/runs/{run_id}") + async def run_detail(run_id: str) -> dict[str, Any]: + stmt = ( + select(TraceEventRow) + .where(TraceEventRow.run_id == run_id) + .order_by(TraceEventRow.id.asc()) + ) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + if not rows: + raise HTTPException(status_code=404, detail="run not found") + return _run_summary(run_id, rows).model_dump(mode="json") + + @app.get("/v1/runs/{run_id}/events") + async def run_events(run_id: str, limit: int = 500) -> list[dict[str, Any]]: + stmt = ( + select(TraceEventRow) + .where(TraceEventRow.run_id == run_id) + .order_by(TraceEventRow.id.asc()) + .limit(_limit(limit)) + ) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + if not rows: + raise HTTPException(status_code=404, detail="run not found") + return [_timeline_item_from_row(row).model_dump(mode="json") for row in rows] + + @app.get("/v1/actions") + async def actions( + graph_id: str | None = None, + case_id: str | None = None, + handoff_id: str | None = None, + change_id: str | None = None, + limit: int = 50, + ) -> list[dict[str, Any]]: + row_limit = _limit(limit, maximum=500) * 20 + stmt = select(TraceEventRow).order_by(TraceEventRow.id.desc()).limit(min(row_limit, 5000)) + if graph_id: + stmt = stmt.where(TraceEventRow.graph_id == graph_id) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + filtered = [ + row + for row in rows + if _action_matches( + row, + graph_id=graph_id, + case_id=case_id, + handoff_id=handoff_id, + change_id=change_id, + ) + ] + return [ + _timeline_item_from_row(row).model_dump(mode="json") + for row in filtered[: _limit(limit)] + ] + + @app.get("/v1/topology") + async def topology(limit: int = 2000) -> dict[str, Any]: + stmt = ( + select(TraceEventRow) + .order_by(TraceEventRow.id.desc()) + .limit(_limit(limit, maximum=5000)) + ) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + loop_ids = set() + for row in rows: + loop_id = row.graph_id or _event_text(row, "graph_id") + if loop_id: + loop_ids.add(loop_id) + nodes = [ + TopologyNode( + node_id="agent-core-collector", + kind="collector", + label="Agent-Core Collector", + status="active", + ) + ] + edges: list[TopologyEdge] = [] + for loop_id in sorted(loop_ids): + nodes.append(TopologyNode(node_id=loop_id, kind="loop", label=loop_id, status="active")) + edges.append( + TopologyEdge( + source_id=loop_id, + target_id="agent-core-collector", + kind="emits_trace", + status="succeeded", + label="ships TraceEvent", + ) + ) + return LoopTopology(nodes=nodes, edges=edges).model_dump(mode="json") + + @app.get("/v1/metrics/daily") + async def daily_metrics(limit: int = 10000) -> list[dict[str, Any]]: + stmt = ( + select(TraceEventRow) + .order_by(TraceEventRow.id.desc()) + .limit(_limit(limit, maximum=10000)) + ) + async with sessionmaker() as session: + rows = list((await session.execute(stmt)).scalars().all()) + grouped: dict[str, dict[str, Any]] = {} + for row in rows: + day = row.received_at.date().isoformat() + bucket = grouped.setdefault( + day, + { + "date": day, + "event_count": 0, + "run_ids": set(), + "graph_counts": {}, + "cost_usd": 0.0, + }, + ) + bucket["event_count"] += 1 + if _run_id(row): + bucket["run_ids"].add(_run_id(row)) + graph = row.graph_id or _event_text(row, "graph_id") or "unknown" + bucket["graph_counts"][graph] = bucket["graph_counts"].get(graph, 0) + 1 + bucket["cost_usd"] += row.cost_usd or 0.0 + rendered = [] + for day, bucket in sorted(grouped.items(), reverse=True): + rendered.append( + { + "date": day, + "event_count": bucket["event_count"], + "run_count": len(bucket["run_ids"]), + "graph_counts": bucket["graph_counts"], + "cost_usd": round(bucket["cost_usd"], 6), + } + ) + return rendered + return app diff --git a/agent_core/contracts/schemas/TraceEvent.schema.json b/agent_core/contracts/schemas/TraceEvent.schema.json index 11ad27a..9f00628 100644 --- a/agent_core/contracts/schemas/TraceEvent.schema.json +++ b/agent_core/contracts/schemas/TraceEvent.schema.json @@ -101,6 +101,58 @@ }, "title": "CostUsage", "type": "object" + }, + "ObservatoryLink": { + "additionalProperties": false, + "description": "Bounded link/reference from an observatory DTO to its source artifact.", + "properties": { + "kind": { + "default": "other", + "enum": [ + "case", + "handoff", + "run", + "trace", + "github_pr", + "github_issue", + "workflow_run", + "deploy", + "knowledge", + "verification", + "external", + "other" + ], + "title": "Kind", + "type": "string" + }, + "label": { + "default": "", + "title": "Label", + "type": "string" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "ref_id": { + "default": "", + "title": "Ref Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "url": { + "default": "", + "title": "Url", + "type": "string" + } + }, + "title": "ObservatoryLink", + "type": "object" } }, "additionalProperties": false, @@ -118,6 +170,42 @@ "default": null, "title": "Agent Role" }, + "case_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Case Id" + }, + "change_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Change Id" + }, + "commit_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Commit Sha" + }, "cost": { "anyOf": [ { @@ -173,6 +261,25 @@ "default": null, "title": "Graph Version" }, + "handoff_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Handoff Id" + }, + "links": { + "items": { + "$ref": "#/$defs/ObservatoryLink" + }, + "title": "Links", + "type": "array" + }, "node_id": { "anyOf": [ { @@ -185,11 +292,60 @@ "default": null, "title": "Node Id" }, + "objective_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Objective Id" + }, + "parent_event_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Parent Event Id" + }, "payload": { "additionalProperties": true, "title": "Payload", "type": "object" }, + "pr_number": { + "anyOf": [ + { + "minimum": 1, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pr Number" + }, + "repository": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Repository" + }, "run_id": { "anyOf": [ { @@ -228,6 +384,18 @@ ], "default": null, "title": "Trace Id" + }, + "workflow_run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Workflow Run Id" } }, "required": [ diff --git a/agent_core/contracts/tracing.py b/agent_core/contracts/tracing.py index 8d59840..91d8b51 100644 --- a/agent_core/contracts/tracing.py +++ b/agent_core/contracts/tracing.py @@ -10,6 +10,7 @@ from agent_core.contracts._base import TraceableModel, VersionedModel, utcnow from agent_core.contracts.models import CostUsage +from agent_core.contracts.observatory import ObservatoryLink def _event_id() -> str: @@ -30,6 +31,20 @@ class TraceEvent(TraceableModel): cost: CostUsage | None = None timestamp: datetime = Field(default_factory=utcnow) + # Optional observatory correlation fields. They are intentionally nullable so + # existing producers remain backward-compatible while richer emitters can tie + # a trace event to CaseService/LHP state, GitHub artifacts, and deploy runs. + case_id: str | None = None + handoff_id: str | None = None + objective_id: str | None = None + change_id: str | None = None + repository: str | None = None + pr_number: int | None = Field(default=None, ge=1) + commit_sha: str | None = None + workflow_run_id: str | None = None + parent_event_id: str | None = None + links: list[ObservatoryLink] = Field(default_factory=list) + class AuditEvent(VersionedModel): """Audit record for a control-plane change (graph edit, promotion, etc.).""" diff --git a/tests/collector/test_collector.py b/tests/collector/test_collector.py index f76b1da..e06bc68 100644 --- a/tests/collector/test_collector.py +++ b/tests/collector/test_collector.py @@ -68,3 +68,75 @@ def test_batch(tmp_path) -> None: with TestClient(_app(tmp_path)) as client: assert client.post("/v1/trace/batch", json=events).json() == {"stored": 2} assert len(client.get("/v1/trace", params={"run_id": "r2"}).json()) == 2 + + +def test_observatory_query_apis(tmp_path) -> None: + events = [ + { + "event_type": "model_call", + "summary": "planner produced a task spec", + "run_id": "run-observatory-1", + "graph_id": "engineering-loop", + "agent_role": "planner", + "case_id": "case_123", + "handoff_id": "handoff_123", + "change_id": "AS215932/agent-core#10", + "repository": "AS215932/agent-core", + "pr_number": 10, + "links": [ + { + "kind": "github_pr", + "label": "agent-core#10", + "url": "https://github.com/AS215932/agent-core/pull/10", + "ref_id": "10", + } + ], + "cost": {"usd": 0.01, "input_tokens": 3, "output_tokens": 4}, + }, + { + "event_type": "tool_call", + "summary": "pytest", + "run_id": "run-observatory-1", + "graph_id": "engineering-loop", + "payload": {"status": "passed"}, + "case_id": "case_123", + "change_id": "AS215932/agent-core#10", + }, + { + "event_type": "knowledge_context_pack", + "summary": "context pack", + "run_id": "run-knowledge-1", + "graph_id": "knowledge", + }, + ] + with TestClient(_app(tmp_path)) as client: + assert client.post("/v1/trace/batch", json=events).json() == {"stored": 3} + + loops = client.get("/v1/loops").json() + assert {loop["loop_id"] for loop in loops} == {"engineering-loop", "knowledge"} + + runs = client.get("/v1/runs").json() + assert {run["run_id"] for run in runs} == {"run-observatory-1", "run-knowledge-1"} + + detail = client.get("/v1/runs/run-observatory-1").json() + assert detail["event_count"] == 2 + assert detail["case_ids"] == ["case_123"] + assert detail["change_ids"] == ["AS215932/agent-core#10"] + assert detail["cost_usd"] == 0.01 + + timeline = client.get("/v1/runs/run-observatory-1/events").json() + assert [item["title"] for item in timeline] == ["model_call", "tool_call"] + assert timeline[0]["links"][0]["kind"] == "github_pr" + + actions = client.get("/v1/actions", params={"case_id": "case_123"}).json() + assert len(actions) == 2 + assert {item["case_id"] for item in actions} == {"case_123"} + + topology = client.get("/v1/topology").json() + assert "agent-core-collector" in {node["node_id"] for node in topology["nodes"]} + assert {edge["kind"] for edge in topology["edges"]} == {"emits_trace"} + + metrics = client.get("/v1/metrics/daily").json() + assert metrics[0]["event_count"] == 3 + assert metrics[0]["run_count"] == 2 + assert metrics[0]["graph_counts"]["engineering-loop"] == 2