From 17f6f7ea6b7dc1ef9c366a374e0ebb57b2d718fc Mon Sep 17 00:00:00 2001 From: David Hyrule Date: Mon, 29 Jun 2026 01:02:33 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20agent-core=20contracts=20package=20(Pha?= =?UTF-8?q?se=201=20/=20=C2=A731=20safe=20milestone)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared typed pydantic contracts + test-only adapters for the engineering, NOC, and knowledge loops, plus descriptive draft GraphSpecs. Additive and behavior-preserving: no existing loop is modified or imported at runtime. Contracts: TaskEnvelope, AgentState, RunContext/RuntimeContext, TraceEvent, AuditEvent, CostUsage, ModelPolicy, RoutingDecision, ToolContract/ToolResult, EvidencePacket/SourceRef, DecisionPacket, HumanApprovalRequest/Decision, PolicyGateResult, FeedbackEvent, ErrorEnvelope, GraphSpec/NodeSpec/EdgeSpec. Adapters: engineering_loop, noc_agent, knowledge. Tests: 25 passing; ruff + mypy clean; CI added. Refs: docs/migration/first-safe-milestone.md, docs/migration/file-level-change-plan.md Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 29 + .gitignore | 11 + README.md | 31 + agent_core/__init__.py | 5 + agent_core/adapters/__init__.py | 12 + agent_core/adapters/engineering_loop.py | 129 +++ agent_core/adapters/knowledge.py | 90 ++ agent_core/adapters/noc_agent.py | 106 +++ agent_core/contracts/__init__.py | 70 ++ agent_core/contracts/_base.py | 52 ++ agent_core/contracts/approval.py | 46 ++ agent_core/contracts/context.py | 36 + agent_core/contracts/decision.py | 27 + agent_core/contracts/errors.py | 27 + agent_core/contracts/evidence.py | 33 + agent_core/contracts/feedback.py | 32 + agent_core/contracts/graph.py | 99 +++ .../graphs/engineering-loop.draft.graph.yaml | 59 ++ .../graphs/knowledge.draft.graph.yaml | 29 + .../graphs/noc-agent.draft.graph.yaml | 46 ++ agent_core/contracts/models.py | 63 ++ .../contracts/schemas/AgentState.schema.json | 325 ++++++++ .../contracts/schemas/AuditEvent.schema.json | 66 ++ .../contracts/schemas/CostUsage.schema.json | 102 +++ .../schemas/DecisionPacket.schema.json | 769 ++++++++++++++++++ .../contracts/schemas/EdgeSpec.schema.json | 131 +++ .../schemas/ErrorEnvelope.schema.json | 76 ++ .../schemas/EvidencePacket.schema.json | 597 ++++++++++++++ .../schemas/FeedbackEvent.schema.json | 198 +++++ .../contracts/schemas/GraphSpec.schema.json | 462 +++++++++++ .../schemas/HumanApprovalDecision.schema.json | 141 ++++ .../schemas/HumanApprovalRequest.schema.json | 155 ++++ .../contracts/schemas/ModelPolicy.schema.json | 68 ++ .../contracts/schemas/NodeSpec.schema.json | 202 +++++ .../schemas/PolicyGateResult.schema.json | 139 ++++ .../schemas/RoutingDecision.schema.json | 188 +++++ .../contracts/schemas/RunContext.schema.json | 138 ++++ .../schemas/RuntimeContext.schema.json | 42 + .../contracts/schemas/SourceRef.schema.json | 88 ++ .../schemas/TaskEnvelope.schema.json | 169 ++++ .../schemas/ToolContract.schema.json | 97 +++ .../contracts/schemas/ToolResult.schema.json | 352 ++++++++ .../contracts/schemas/TraceEvent.schema.json | 238 ++++++ agent_core/contracts/state.py | 28 + agent_core/contracts/task.py | 29 + agent_core/contracts/tools.py | 40 + agent_core/contracts/tracing.py | 43 + pyproject.toml | 33 + scripts/export_schemas.py | 40 + .../fixtures/approval_decision.sample.json | 7 + .../fixtures/change_proposal.sample.json | 16 + .../fixtures/context_pack.sample.json | 23 + .../fixtures/evidence_items.sample.json | 14 + .../adapters/fixtures/graph_state.sample.json | 8 + .../adapters/fixtures/loop_trace.sample.json | 26 + .../adapters/fixtures/role_review.sample.json | 11 + tests/adapters/test_engineering_adapter.py | 36 + tests/adapters/test_knowledge_adapter.py | 29 + tests/adapters/test_noc_adapter.py | 42 + tests/conftest.py | 18 + tests/contracts/test_graphspecs_parse.py | 36 + tests/contracts/test_invalid_rejection.py | 32 + tests/contracts/test_json_roundtrip.py | 24 + tests/contracts/test_no_heavy_imports.py | 14 + tests/contracts/test_schema_validation.py | 51 ++ tests/schemas/test_schema_export.py | 31 + 66 files changed, 6406 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 agent_core/__init__.py create mode 100644 agent_core/adapters/__init__.py create mode 100644 agent_core/adapters/engineering_loop.py create mode 100644 agent_core/adapters/knowledge.py create mode 100644 agent_core/adapters/noc_agent.py create mode 100644 agent_core/contracts/__init__.py create mode 100644 agent_core/contracts/_base.py create mode 100644 agent_core/contracts/approval.py create mode 100644 agent_core/contracts/context.py create mode 100644 agent_core/contracts/decision.py create mode 100644 agent_core/contracts/errors.py create mode 100644 agent_core/contracts/evidence.py create mode 100644 agent_core/contracts/feedback.py create mode 100644 agent_core/contracts/graph.py create mode 100644 agent_core/contracts/graphs/engineering-loop.draft.graph.yaml create mode 100644 agent_core/contracts/graphs/knowledge.draft.graph.yaml create mode 100644 agent_core/contracts/graphs/noc-agent.draft.graph.yaml create mode 100644 agent_core/contracts/models.py create mode 100644 agent_core/contracts/schemas/AgentState.schema.json create mode 100644 agent_core/contracts/schemas/AuditEvent.schema.json create mode 100644 agent_core/contracts/schemas/CostUsage.schema.json create mode 100644 agent_core/contracts/schemas/DecisionPacket.schema.json create mode 100644 agent_core/contracts/schemas/EdgeSpec.schema.json create mode 100644 agent_core/contracts/schemas/ErrorEnvelope.schema.json create mode 100644 agent_core/contracts/schemas/EvidencePacket.schema.json create mode 100644 agent_core/contracts/schemas/FeedbackEvent.schema.json create mode 100644 agent_core/contracts/schemas/GraphSpec.schema.json create mode 100644 agent_core/contracts/schemas/HumanApprovalDecision.schema.json create mode 100644 agent_core/contracts/schemas/HumanApprovalRequest.schema.json create mode 100644 agent_core/contracts/schemas/ModelPolicy.schema.json create mode 100644 agent_core/contracts/schemas/NodeSpec.schema.json create mode 100644 agent_core/contracts/schemas/PolicyGateResult.schema.json create mode 100644 agent_core/contracts/schemas/RoutingDecision.schema.json create mode 100644 agent_core/contracts/schemas/RunContext.schema.json create mode 100644 agent_core/contracts/schemas/RuntimeContext.schema.json create mode 100644 agent_core/contracts/schemas/SourceRef.schema.json create mode 100644 agent_core/contracts/schemas/TaskEnvelope.schema.json create mode 100644 agent_core/contracts/schemas/ToolContract.schema.json create mode 100644 agent_core/contracts/schemas/ToolResult.schema.json create mode 100644 agent_core/contracts/schemas/TraceEvent.schema.json create mode 100644 agent_core/contracts/state.py create mode 100644 agent_core/contracts/task.py create mode 100644 agent_core/contracts/tools.py create mode 100644 agent_core/contracts/tracing.py create mode 100644 pyproject.toml create mode 100644 scripts/export_schemas.py create mode 100644 tests/adapters/fixtures/approval_decision.sample.json create mode 100644 tests/adapters/fixtures/change_proposal.sample.json create mode 100644 tests/adapters/fixtures/context_pack.sample.json create mode 100644 tests/adapters/fixtures/evidence_items.sample.json create mode 100644 tests/adapters/fixtures/graph_state.sample.json create mode 100644 tests/adapters/fixtures/loop_trace.sample.json create mode 100644 tests/adapters/fixtures/role_review.sample.json create mode 100644 tests/adapters/test_engineering_adapter.py create mode 100644 tests/adapters/test_knowledge_adapter.py create mode 100644 tests/adapters/test_noc_adapter.py create mode 100644 tests/conftest.py create mode 100644 tests/contracts/test_graphspecs_parse.py create mode 100644 tests/contracts/test_invalid_rejection.py create mode 100644 tests/contracts/test_json_roundtrip.py create mode 100644 tests/contracts/test_no_heavy_imports.py create mode 100644 tests/contracts/test_schema_validation.py create mode 100644 tests/schemas/test_schema_export.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6880545 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install + run: | + python -m pip install --upgrade pip + pip install -e '.[dev]' + - name: Lint + run: ruff check . + - name: Typecheck + run: mypy agent_core + - name: Test + run: pytest -q + - name: Schema export is up to date + run: | + python scripts/export_schemas.py + git diff --exit-code -- agent_core/contracts/schemas || (echo "schemas out of date: run scripts/export_schemas.py" && exit 1) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6223e91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +__pycache__/ +*.py[cod] +.venv/ +venv/ +.mypy_cache/ +.ruff_cache/ +.pytest_cache/ +dist/ +build/ +*.egg-info/ +.coverage diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6e5884 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# agent-core + +Shared, dependency-light **typed contracts** for the AS215932 Agent Runtime Framework. + +This is the **§31 safe milestone** (Phase 1) of the framework consolidation described in +`../docs/migration/first-safe-milestone.md`. It introduces standard contracts **without +changing any existing loop's behavior**: + +- `agent_core/contracts/` — pydantic v2 models (JSON-serializable, schema-versioned). + Importing them pulls in **only pydantic** (no langgraph / pydantic-ai / db). +- `agent_core/adapters/` — pure mapping functions that convert each loop's existing + shapes (engineering-loop, NOC agent, knowledge) into the shared contracts. **Imported + by tests only**; not wired into any loop's runtime. +- `agent_core/contracts/graphs/` — *descriptive* draft `GraphSpec`s of the loops' current + LangGraph topology (no compiler yet). + +## Scope + +In: contracts, adapters (test-only), draft GraphSpecs, tests, CI. +Out (later phases): runtime, GraphSpec compiler, model router, tool/MCP registries, +memory store, learning substrate, judges, policy gates, control-plane API/GUI. + +## Develop + +```bash +uv venv && uv pip install -e '.[dev]' # or: python -m venv .venv && pip install -e '.[dev]' +ruff check . && mypy agent_core && pytest -q +python scripts/export_schemas.py # regenerate committed JSON schemas +``` + +See `../docs/` for the full inventory, contract-gap analysis, and migration plan. diff --git a/agent_core/__init__.py b/agent_core/__init__.py new file mode 100644 index 0000000..bf0eddf --- /dev/null +++ b/agent_core/__init__.py @@ -0,0 +1,5 @@ +"""agent-core: shared typed contracts for the AS215932 Agent Runtime Framework.""" + +from __future__ import annotations + +__version__ = "0.1.0" diff --git a/agent_core/adapters/__init__.py b/agent_core/adapters/__init__.py new file mode 100644 index 0000000..62cd3e8 --- /dev/null +++ b/agent_core/adapters/__init__.py @@ -0,0 +1,12 @@ +"""Adapters: pure mapping functions from each loop's shapes to agent-core contracts. + +These are imported by tests only — they are NOT wired into any loop's runtime in this +milestone. Each function takes a JSON-serializable dict (the loop's existing shape) and +returns a typed contract. +""" + +from __future__ import annotations + +from agent_core.adapters import engineering_loop, knowledge, noc_agent + +__all__ = ["engineering_loop", "knowledge", "noc_agent"] diff --git a/agent_core/adapters/engineering_loop.py b/agent_core/adapters/engineering_loop.py new file mode 100644 index 0000000..f3f1ec4 --- /dev/null +++ b/agent_core/adapters/engineering_loop.py @@ -0,0 +1,129 @@ +"""Map engineering-loop shapes -> agent-core contracts (no runtime wiring). + +Source shapes: hyrule_engineering_loop.state.GraphState, llm.RoleReviewOutput / +FileMutation, backend CostReport (in backend_results[].cost), trace.loop_trace.json. +""" + +from __future__ import annotations + +from typing import Any + +from agent_core.contracts.decision import DecisionPacket +from agent_core.contracts.models import CostUsage +from agent_core.contracts.task import TaskEnvelope +from agent_core.contracts.tools import ToolResult +from agent_core.contracts.tracing import TraceEvent + +GRAPH_ID = "engineering-loop" + + +def task_envelope_from_graph_state(state: dict[str, Any]) -> TaskEnvelope: + return TaskEnvelope.model_validate( + { + "task_id": state["change_id"], + "task_class": state.get("change_class", "mixed"), + "source": GRAPH_ID, + "risk_level": state.get("risk_level", "low"), + "customer_impact": state.get("customer_impact"), + "input": { + "source_of_truth_files": state.get("source_of_truth_files", []), + "feature_request": state.get("feature_request"), + }, + "graph_id": GRAPH_ID, + } + ) + + +def decision_from_role_review(role: str, review: dict[str, Any]) -> DecisionPacket: + approved = review.get("approved") + return DecisionPacket.model_validate( + { + "decision": "approve" if approved else "reject", + "approved": approved, + "rationale": review.get("notes", ""), + "proposed_actions": [ + {"path": m.get("path"), "operation": m.get("operation", "create")} + for m in review.get("proposed_mutations", []) or [] + ], + "validation_errors": review.get("validation_errors", []) or [], + "agent_role": role, + "node_id": role, + "graph_id": GRAPH_ID, + } + ) + + +def cost_usage_from_backend_result(backend_result: dict[str, Any]) -> CostUsage: + cost = backend_result.get("cost", {}) or {} + return CostUsage.model_validate( + { + "model": cost.get("model") or backend_result.get("model"), + "provider": cost.get("provider"), + "input_tokens": cost.get("input_tokens"), + "output_tokens": cost.get("output_tokens"), + "usd": cost.get("usd"), + } + ) + + +def tool_result_from_gate(gate: dict[str, Any]) -> ToolResult: + return ToolResult.model_validate( + { + "tool": str(gate.get("command")), + "ok": gate.get("status") == "passed", + "output": {"status": gate.get("status"), "returncode": gate.get("returncode")}, + "node_id": "gate_execution", + "graph_id": GRAPH_ID, + } + ) + + +def trace_events_from_loop_trace( + trace: dict[str, Any], *, run_id: str | None = None +) -> list[TraceEvent]: + events: list[TraceEvent] = [] + for item in trace.get("llm_outputs", []) or []: + events.append( + TraceEvent.model_validate( + { + "event_type": "model_call", + "node_id": item.get("role"), + "agent_role": item.get("role"), + "summary": f"role review approved={item.get('approved')}", + "payload": item, + "run_id": run_id, + "graph_id": GRAPH_ID, + } + ) + ) + for gate in trace.get("gate_results", []) or []: + events.append( + TraceEvent.model_validate( + { + "event_type": "tool_call", + "node_id": "gate_execution", + "summary": str(gate.get("command")), + "payload": gate, + "run_id": run_id, + "graph_id": GRAPH_ID, + } + ) + ) + for backend_result in trace.get("backend_results", []) or []: + events.append( + TraceEvent.model_validate( + { + "event_type": "backend_execution", + "node_id": "delegate_implementation", + "summary": ( + f"backend={backend_result.get('backend')} " + f"status={backend_result.get('status')}" + ), + "payload": backend_result, + "cost": cost_usage_from_backend_result(backend_result).model_dump(), + "run_id": run_id, + "graph_id": GRAPH_ID, + } + ) + ) + return events diff --git a/agent_core/adapters/knowledge.py b/agent_core/adapters/knowledge.py new file mode 100644 index 0000000..899580c --- /dev/null +++ b/agent_core/adapters/knowledge.py @@ -0,0 +1,90 @@ +"""Map knowledge loop shapes -> agent-core contracts (no runtime wiring). + +Source shapes: knowledge context-pack (schema/context-pack.schema.json) + CLI request. +""" + +from __future__ import annotations + +import hashlib +from typing import Any + +from agent_core.contracts.evidence import EvidencePacket +from agent_core.contracts.task import TaskEnvelope +from agent_core.contracts.tracing import TraceEvent + +GRAPH_ID = "knowledge" +_AUTHORITY_ORDER = ["A0", "A1", "A2", "A3", "A4", "A5"] + + +def task_envelope_from_context_request( + task: str, + role: str = "engineering_loop", + risk_level: str = "low", + task_id: str | None = None, +) -> TaskEnvelope: + resolved_id = task_id or "know_" + hashlib.sha256(task.encode()).hexdigest()[:8] + return TaskEnvelope.model_validate( + { + "task_id": resolved_id, + "task_class": "knowledge_retrieval", + "source": GRAPH_ID, + "risk_level": risk_level, + "input": {"task": task, "role": role}, + "graph_id": GRAPH_ID, + } + ) + + +def evidence_from_context_pack(pack: dict[str, Any]) -> EvidencePacket: + sources: list[dict[str, Any]] = [] + best: str | None = None + for ref in pack.get("included_refs", []) or []: + authority = ref.get("authority") + sources.append( + { + "ref": ref.get("ref") or ref.get("doc_id") or ref.get("doc_path") or "", + "authority": authority, + "kind": ref.get("kind"), + "commit_sha": ref.get("commit_sha"), + "review_status": ref.get("review_status"), + } + ) + if authority in _AUTHORITY_ORDER and ( + best is None or _AUTHORITY_ORDER.index(authority) < _AUTHORITY_ORDER.index(best) + ): + best = authority + return EvidencePacket.model_validate( + { + "sources": sources, + "authority_max": best, + "unresolved_questions": pack.get("unresolved_questions", []) or [], + "metadata": { + "context_pack_id": pack.get("id"), + "retrieval_version": pack.get("retrieval_version"), + "policy_version": pack.get("policy_version"), + "knowledge_snapshot": pack.get("knowledge_snapshot"), + }, + "graph_id": GRAPH_ID, + } + ) + + +def trace_event_from_context_pack( + pack: dict[str, Any], *, run_id: str | None = None +) -> TraceEvent: + ref_count = len(pack.get("included_refs", []) or []) + return TraceEvent.model_validate( + { + "event_type": "knowledge_context_pack", + "node_id": "context_pack", + "summary": f"context pack {pack.get('id')} ({ref_count} refs)", + "payload": { + "id": pack.get("id"), + "retrieval_version": pack.get("retrieval_version"), + "policy_version": pack.get("policy_version"), + "policy_decision": pack.get("policy_decision"), + }, + "run_id": run_id, + "graph_id": GRAPH_ID, + } + ) diff --git a/agent_core/adapters/noc_agent.py b/agent_core/adapters/noc_agent.py new file mode 100644 index 0000000..f923983 --- /dev/null +++ b/agent_core/adapters/noc_agent.py @@ -0,0 +1,106 @@ +"""Map NOC agent shapes -> agent-core contracts (no runtime wiring). + +Source shapes: app/graph/state.py ChangeProposal / EvidenceItem / ApprovalDecision, +app/model_metrics.py. +""" + +from __future__ import annotations + +from typing import Any + +from agent_core.contracts.approval import HumanApprovalDecision +from agent_core.contracts.decision import DecisionPacket +from agent_core.contracts.evidence import EvidencePacket +from agent_core.contracts.models import CostUsage +from agent_core.contracts.task import TaskEnvelope +from agent_core.contracts.tools import ToolResult + +GRAPH_ID = "noc-agent" + + +def task_envelope_from_alert( + alert: dict[str, Any], + incident_id: str, + resource_id: str = "", + risk_level: str = "medium", +) -> TaskEnvelope: + return TaskEnvelope.model_validate( + { + "task_id": incident_id, + "task_class": alert.get("rule") or alert.get("alertname") or "noc_triage", + "source": GRAPH_ID, + "risk_level": risk_level, + "input": {"normalized_alert": alert, "resource_id": resource_id}, + "graph_id": GRAPH_ID, + } + ) + + +def tool_result_from_evidence_item(item: dict[str, Any]) -> ToolResult: + payload = dict(item.get("payload", {}) or {}) + return ToolResult.model_validate( + { + "tool": item.get("tool", ""), + "ok": True, + "output": { + "summary": item.get("summary"), + "direct_measurement": item.get("direct_measurement", False), + **payload, + }, + "node_id": "evidence_validation", + "graph_id": GRAPH_ID, + } + ) + + +def evidence_from_items(items: list[dict[str, Any]]) -> EvidencePacket: + return EvidencePacket.model_validate( + { + "tool_results": [tool_result_from_evidence_item(i).model_dump() for i in items], + "graph_id": GRAPH_ID, + } + ) + + +def decision_from_change_proposal(proposal: dict[str, Any]) -> DecisionPacket: + remediation = [{"remediation": r} for r in proposal.get("proposed_remediation", []) or []] + structured = list(proposal.get("structured_actions", []) or []) + sources = [{"ref": ref} for ref in proposal.get("evidence_refs", []) or []] + return DecisionPacket.model_validate( + { + "decision": "propose_remediation", + "confidence": proposal.get("confidence"), + "rationale": proposal.get("assessment") or proposal.get("root_cause_hypothesis", ""), + "proposed_actions": remediation + structured, + "evidence": { + "sources": sources, + "metadata": {"drift_findings": proposal.get("drift_findings", []) or []}, + }, + "node_id": "proposal_build", + "graph_id": GRAPH_ID, + } + ) + + +def approval_decision_from_noc(decision: dict[str, Any]) -> HumanApprovalDecision: + payload: dict[str, Any] = { + "request_id": decision.get("incident_id", ""), + "decision": decision["decision"], + "approver": decision.get("operator", "unknown"), + "rationale": decision.get("comment", ""), + "graph_id": GRAPH_ID, + } + if decision.get("decided_at"): + payload["decided_at"] = decision["decided_at"] + return HumanApprovalDecision.model_validate(payload) + + +def cost_usage_from_model_metrics(metrics: dict[str, Any]) -> CostUsage: + return CostUsage.model_validate( + { + "model": metrics.get("model") or metrics.get("active_model"), + "provider": metrics.get("provider"), + "latency_ms": metrics.get("latency_ms"), + "fallback_used": bool(metrics.get("fallback_attempts") or metrics.get("fallback_used")), + } + ) diff --git a/agent_core/contracts/__init__.py b/agent_core/contracts/__init__.py new file mode 100644 index 0000000..d0ede4b --- /dev/null +++ b/agent_core/contracts/__init__.py @@ -0,0 +1,70 @@ +"""agent-core contracts — import pulls in pydantic only (no heavy runtime deps).""" + +from __future__ import annotations + +from agent_core.contracts._base import ( + SCHEMA_VERSION, + ActorRole, + AuthorityTier, + DifficultyBand, + RiskLevel, + ToolRiskTier, + TraceableModel, + VersionedModel, + utcnow, +) +from agent_core.contracts.approval import ( + HumanApprovalDecision, + HumanApprovalRequest, + PolicyGateResult, +) +from agent_core.contracts.context import RunContext, RuntimeContext +from agent_core.contracts.decision import DecisionPacket +from agent_core.contracts.errors import ErrorEnvelope +from agent_core.contracts.evidence import EvidencePacket, SourceRef +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.state import AgentState +from agent_core.contracts.task import TaskEnvelope +from agent_core.contracts.tools import ToolContract, ToolResult +from agent_core.contracts.tracing import AuditEvent, TraceEvent + +__all__ = [ + # base + "SCHEMA_VERSION", + "ActorRole", + "AuthorityTier", + "DifficultyBand", + "RiskLevel", + "ToolRiskTier", + "TraceableModel", + "VersionedModel", + "utcnow", + # core + "TaskEnvelope", + "AgentState", + "RunContext", + "RuntimeContext", + "TraceEvent", + "AuditEvent", + "CostUsage", + "ModelPolicy", + "RoutingDecision", + "ToolContract", + "ToolResult", + "SourceRef", + "EvidencePacket", + "DecisionPacket", + "HumanApprovalRequest", + "HumanApprovalDecision", + "PolicyGateResult", + "FeedbackEvent", + "ErrorEnvelope", + # graph (draft) + "GraphSpec", + "NodeSpec", + "EdgeSpec", + "NodeKind", + "EdgeMode", +] diff --git a/agent_core/contracts/_base.py b/agent_core/contracts/_base.py new file mode 100644 index 0000000..11389e1 --- /dev/null +++ b/agent_core/contracts/_base.py @@ -0,0 +1,52 @@ +"""Base model + shared type aliases for all agent-core contracts. + +Design rules (see ../../../docs/migration/first-safe-milestone.md): +- pydantic v2 only; no heavy runtime deps. +- every contract is JSON-serializable and carries ``schema_version``. +- runtime/event contracts also carry optional provenance labels. +""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Literal + +from pydantic import BaseModel, ConfigDict + +SCHEMA_VERSION = "0.1.0" + +# Shared, deliberately small vocabularies (supersets across loops). +RiskLevel = Literal["low", "medium", "high", "critical"] +ToolRiskTier = Literal["read_only", "low_write", "high_write", "prod_change"] +DifficultyBand = Literal["cheap", "balanced", "strong", "frontier"] +AuthorityTier = Literal["A0", "A1", "A2", "A3", "A4", "A5"] +ActorRole = Literal["end_user", "operator", "senior", "system"] + + +def utcnow() -> datetime: + """Timezone-aware UTC now (used as default_factory).""" + return datetime.now(UTC) + + +class VersionedModel(BaseModel): + """Base for every contract: strict, schema-versioned, JSON-serializable.""" + + model_config = ConfigDict(extra="forbid") + + schema_version: str = SCHEMA_VERSION + + +class TraceableModel(VersionedModel): + """Base for runtime/event contracts: adds optional provenance labels. + + Labels let any emitted object be tied back to an environment, graph version, + node, agent role, and run/trace without coupling to a runtime. + """ + + environment: str | None = None + graph_id: str | None = None + graph_version: str | None = None + node_id: str | None = None + agent_role: str | None = None + run_id: str | None = None + trace_id: str | None = None diff --git a/agent_core/contracts/approval.py b/agent_core/contracts/approval.py new file mode 100644 index 0000000..67af8cb --- /dev/null +++ b/agent_core/contracts/approval.py @@ -0,0 +1,46 @@ +"""Human approval + policy gate contracts.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal + +from pydantic import Field + +from agent_core.contracts._base import RiskLevel, TraceableModel, utcnow + + +class HumanApprovalRequest(TraceableModel): + """A pause/approval request raised at a graph interrupt.""" + + request_id: str + reason: str + risk_level: RiskLevel = "low" + required_role: str | None = None + payload: dict[str, Any] = Field(default_factory=dict) + created_at: datetime = Field(default_factory=utcnow) + expires_at: datetime | None = None + + +class HumanApprovalDecision(TraceableModel): + """An operator's approval/rejection. Maps from NOC ``ApprovalDecision``.""" + + request_id: str + decision: Literal["approved", "rejected", "acknowledged"] + approver: str + approver_role: str | None = None + rationale: str = "" + decided_at: datetime = Field(default_factory=utcnow) + + +class PolicyGateResult(TraceableModel): + """Structured safety/approval gate decision.""" + + allow: bool + require_human_approval: bool = False + require_stronger_judge: bool = False + require_more_evidence: bool = False + require_runbook: bool = False + require_ticket_link: bool = False + reason: str = "" + audit: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/context.py b/agent_core/contracts/context.py new file mode 100644 index 0000000..1df2cb8 --- /dev/null +++ b/agent_core/contracts/context.py @@ -0,0 +1,36 @@ +"""RunContext (per-run metadata) and RuntimeContext (environment-level deps).""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import VersionedModel, utcnow + + +class RunContext(VersionedModel): + """Runtime metadata for a single run; the provenance carrier.""" + + run_id: str + trace_id: str | None = None + environment: str | None = None + deployment_slot: str | None = None + graph_id: str | None = None + graph_version: str | None = None + node_id: str | None = None + agent_role: str | None = None + model_policy_ref: str | None = None + instruction_pack_ref: str | None = None + started_at: datetime = Field(default_factory=utcnow) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class RuntimeContext(VersionedModel): + """Environment-level runtime dependencies/config (not per-run).""" + + environment: str = "dev" + deployment_slot: str | None = None + feature_flags: dict[str, bool] = Field(default_factory=dict) + config: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/decision.py b/agent_core/contracts/decision.py new file mode 100644 index 0000000..2e56666 --- /dev/null +++ b/agent_core/contracts/decision.py @@ -0,0 +1,27 @@ +"""Decision packet: an agent's decision, confidence, rationale, next action.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import TraceableModel +from agent_core.contracts.evidence import EvidencePacket + + +class DecisionPacket(TraceableModel): + """Standard agent decision output. + + Maps from engineering-loop ``RoleReviewOutput`` (approved/notes/mutations) and + NOC ``ChangeProposal`` (assessment/root_cause/confidence/structured_actions). + """ + + decision: str + approved: bool | None = None + confidence: float | None = None + rationale: str = "" + proposed_actions: list[dict[str, Any]] = Field(default_factory=list) + validation_errors: list[dict[str, Any]] = Field(default_factory=list) + next_action: str | None = None + evidence: EvidencePacket | None = None diff --git a/agent_core/contracts/errors.py b/agent_core/contracts/errors.py new file mode 100644 index 0000000..4df844c --- /dev/null +++ b/agent_core/contracts/errors.py @@ -0,0 +1,27 @@ +"""Standard error envelope (superset of NOC SafeError + engineering-loop errors).""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import VersionedModel, utcnow + + +class ErrorEnvelope(VersionedModel): + """Structured, serializable error shape. + + Maps from NOC ``SafeError(category, public_message, operator_next_steps)`` and + engineering-loop ``*Error`` RuntimeError subclasses. + """ + + error_type: str + message: str + category: str | None = None + public_message: str | None = None + retryable: bool = False + operator_next_steps: str | None = None + details: dict[str, Any] = Field(default_factory=dict) + occurred_at: datetime = Field(default_factory=utcnow) diff --git a/agent_core/contracts/evidence.py b/agent_core/contracts/evidence.py new file mode 100644 index 0000000..0634d07 --- /dev/null +++ b/agent_core/contracts/evidence.py @@ -0,0 +1,33 @@ +"""Evidence packet: sources, tool outputs, references behind an answer/decision.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import AuthorityTier, TraceableModel, VersionedModel +from agent_core.contracts.tools import ToolResult + + +class SourceRef(VersionedModel): + """A cited source with optional A0-A5 authority (from the knowledge loop).""" + + ref: str + kind: str | None = None + authority: AuthorityTier | None = None + commit_sha: str | None = None + review_status: str | None = None + excerpt: str | None = None + + +class EvidencePacket(TraceableModel): + """Sources + tool outputs + references that ground a decision/answer.""" + + sources: list[SourceRef] = Field(default_factory=list) + tool_results: list[ToolResult] = Field(default_factory=list) + code_refs: list[str] = Field(default_factory=list) + logs: list[str] = Field(default_factory=list) + authority_max: AuthorityTier | None = None + unresolved_questions: list[str] = Field(default_factory=list) + metadata: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/feedback.py b/agent_core/contracts/feedback.py new file mode 100644 index 0000000..a50eab3 --- /dev/null +++ b/agent_core/contracts/feedback.py @@ -0,0 +1,32 @@ +"""FeedbackEvent: raw human/operator/automated feedback (Learning Substrate entry).""" + +from __future__ import annotations + +from datetime import datetime + +from pydantic import Field + +from agent_core.contracts._base import ActorRole, TraceableModel, utcnow + + +class FeedbackEvent(TraceableModel): + """Raw feedback on an answer/run/node/model. + + First source: NOC ``operator_feedback`` (recorded today, unused downstream) and + engineering-loop curated-lesson proposals. Normalizes later into RewardSignal. + """ + + feedback_id: str + target_type: str = "answer" + target_id: str | None = None + actor_id: str | None = None + actor_role: ActorRole = "operator" + signal_type: str = "good" + score: float | None = None + categories: list[str] = Field(default_factory=list) + correction_text: str = "" + rationale: str = "" + promote_to_memory: bool = False + promote_to_eval: bool = False + promote_to_judge_alignment: bool = False + created_at: datetime = Field(default_factory=utcnow) diff --git a/agent_core/contracts/graph.py b/agent_core/contracts/graph.py new file mode 100644 index 0000000..3f882a6 --- /dev/null +++ b/agent_core/contracts/graph.py @@ -0,0 +1,99 @@ +"""Declarative graph specs (DRAFT models only — no compiler in this milestone).""" + +from __future__ import annotations + +from typing import Any, Literal + +from pydantic import Field + +from agent_core.contracts._base import RiskLevel, VersionedModel + +NodeKind = Literal[ + "agent", + "judge", + "tool_executor", + "mcp", + "retriever", + "memory", + "policy", + "human_approval", + "subgraph", + "parallel_council", + "synthesizer", + "system_node", + "finalizer", +] + +EdgeMode = Literal[ + "normal", + "conditional", + "parallel", + "fallback", + "retry", + "approval", + "interrupt", + "terminal", +] + + +class NodeSpec(VersionedModel): + """Declarative node spec.""" + + id: str + kind: NodeKind + implementation: str | None = None + agent_ref: str | None = None + judge_ref: str | None = None + policy_ref: str | None = None + subgraph_ref: str | None = None + input_contract: str | None = None + output_contract: str | None = None + model_policy_ref: str | None = None + tool_bindings: list[str] = Field(default_factory=list) + mcp_bindings: list[str] = Field(default_factory=list) + memory_namespaces: list[str] = Field(default_factory=list) + risk_tier: RiskLevel | None = None + timeout_seconds: float | None = None + retry_policy: dict[str, Any] = Field(default_factory=dict) + enabled: bool = True + config: dict[str, Any] = Field(default_factory=dict) + + +class EdgeSpec(VersionedModel): + """Declarative edge spec.""" + + id: str + source: str + target: str + mode: EdgeMode = "normal" + router_ref: str | None = None + candidates: list[str] = Field(default_factory=list) + condition: str | dict[str, Any] | None = None + weight: float | None = None + enabled: bool = True + max_attempts: int | None = None + timeout_seconds: float | None = None + guardrails: dict[str, Any] = Field(default_factory=dict) + metadata: dict[str, Any] = Field(default_factory=dict) + + +class GraphSpec(VersionedModel): + """Declarative graph topology spec (descriptive in this milestone).""" + + graph_id: str + version_name: str + entrypoint: str + description: str = "" + owner: str | None = None + state_schema: str | None = None + task_schema: str | None = None + nodes: list[NodeSpec] = Field(default_factory=list) + edges: list[EdgeSpec] = Field(default_factory=list) + subgraphs: list[str] = Field(default_factory=list) + model_policies: dict[str, Any] = Field(default_factory=dict) + judge_policies: dict[str, Any] = Field(default_factory=dict) + tool_bindings: dict[str, Any] = Field(default_factory=dict) + mcp_bindings: dict[str, Any] = Field(default_factory=dict) + learning_config: dict[str, Any] = Field(default_factory=dict) + deployment: dict[str, Any] = Field(default_factory=dict) + risk_policy: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/graphs/engineering-loop.draft.graph.yaml b/agent_core/contracts/graphs/engineering-loop.draft.graph.yaml new file mode 100644 index 0000000..f35c173 --- /dev/null +++ b/agent_core/contracts/graphs/engineering-loop.draft.graph.yaml @@ -0,0 +1,59 @@ +# DESCRIPTIVE draft GraphSpec of the engineering-loop's CURRENT LangGraph topology. +# Source of truth: engineering-loop/src/hyrule_engineering_loop/graph.py (build_graph). +# Descriptive only — there is no compiler in this milestone. +schema_version: "0.1.0" +graph_id: engineering-loop +version_name: draft-from-graph.py +description: >- + 20-node engineering loop: classification -> planner -> 6 role reviewers -> + repo adapter/worktree -> gates -> policy -> role judgment -> + promotion|package_pr -> human signoff -> reflection. +owner: AS215932/engineering-loop +entrypoint: classification +state_schema: hyrule_engineering_loop.state.GraphState +nodes: + - {id: classification, kind: system_node} + - {id: planner, kind: agent, agent_ref: planner} + - {id: network_architect, kind: agent, agent_ref: network_architect, model_policy_ref: model-policy.yml#network_architect, risk_tier: high} + - {id: systems_engineer, kind: agent, agent_ref: systems_engineer, model_policy_ref: model-policy.yml#systems_engineer} + - {id: devops_netops, kind: agent, agent_ref: devops_netops, model_policy_ref: model-policy.yml#devops_netops} + - {id: security_auditor, kind: agent, agent_ref: security_auditor, model_policy_ref: model-policy.yml#security_auditor, risk_tier: high} + - {id: finops_integrity, kind: agent, agent_ref: finops_integrity, model_policy_ref: model-policy.yml#finops_integrity} + - {id: virtual_lab_chaos, kind: agent, agent_ref: virtual_lab_chaos, model_policy_ref: model-policy.yml#virtual_lab_chaos} + - {id: repo_adapter, kind: system_node} + - {id: worktree_setup, kind: system_node} + - {id: pre_gate_policy, kind: policy, policy_ref: policy.py} + - {id: delegate_implementation, kind: tool_executor} + - {id: gate_execution, kind: tool_executor} + - {id: workspace_cleanup, kind: system_node} + - {id: policy, kind: policy, policy_ref: policy.py} + - {id: role_judgment, kind: judge, judge_ref: judgment.py} + - {id: promotion, kind: tool_executor, risk_tier: high} + - {id: package_pr, kind: tool_executor} + - {id: human_signoff, kind: human_approval} + - {id: reflection, kind: finalizer} +edges: + - {id: e_start, source: classification, target: planner, mode: normal} + - {id: e_plan_fanout, source: planner, target: network_architect, mode: conditional, candidates: [network_architect, systems_engineer, devops_netops, security_auditor, finops_integrity, virtual_lab_chaos]} + - {id: e_na_join, source: network_architect, target: repo_adapter, mode: normal} + - {id: e_se_join, source: systems_engineer, target: repo_adapter, mode: normal} + - {id: e_dn_join, source: devops_netops, target: repo_adapter, mode: normal} + - {id: e_sa_join, source: security_auditor, target: repo_adapter, mode: normal} + - {id: e_fi_join, source: finops_integrity, target: repo_adapter, mode: normal} + - {id: e_vlc_join, source: virtual_lab_chaos, target: repo_adapter, mode: normal} + - {id: e_repo_wt, source: repo_adapter, target: worktree_setup, mode: conditional} + - {id: e_wt_pregate, source: worktree_setup, target: pre_gate_policy, mode: conditional} + - {id: e_pregate_delegate, source: pre_gate_policy, target: delegate_implementation, mode: conditional} + - {id: e_delegate_gate, source: delegate_implementation, target: gate_execution, mode: conditional} + - {id: e_gate_cleanup, source: gate_execution, target: workspace_cleanup, mode: normal} + - {id: e_cleanup_policy, source: workspace_cleanup, target: policy, mode: conditional} + - {id: e_policy_judgment, source: policy, target: role_judgment, mode: conditional} + - {id: e_judgment_promotion, source: role_judgment, target: promotion, mode: conditional, candidates: [promotion, package_pr]} + - {id: e_promotion_signoff, source: promotion, target: human_signoff, mode: approval} + - {id: e_package_reflection, source: package_pr, target: reflection, mode: normal} + - {id: e_signoff_reflection, source: human_signoff, target: reflection, mode: normal} + - {id: e_reflection_end, source: reflection, target: END, mode: terminal} +risk_policy: + human_signoff_required_for: [promotion, prod_change] +deployment: + default_backend: mock diff --git a/agent_core/contracts/graphs/knowledge.draft.graph.yaml b/agent_core/contracts/graphs/knowledge.draft.graph.yaml new file mode 100644 index 0000000..f0a3aaa --- /dev/null +++ b/agent_core/contracts/graphs/knowledge.draft.graph.yaml @@ -0,0 +1,29 @@ +# DESCRIPTIVE draft GraphSpec of the knowledge loop's retrieval/enrichment pipeline. +# knowledge is a service (CLI + MCP server), not a LangGraph graph; this models the +# logical context-pack retrieval path. Source: knowledge/src/hyrule_knowledge/*. +schema_version: "0.1.0" +graph_id: knowledge +version_name: draft-logical-pipeline +description: >- + Governed retrieval + enrichment: ingest -> retrieve (exact/graph/FTS) -> + policy gate (A0-A5, deny-by-default) -> context-pack assembly -> finalize; + enrichment + export are side paths. +owner: AS215932/knowledge +entrypoint: retrieve +nodes: + - {id: ingest, kind: system_node} + - {id: retrieve, kind: retriever, config: {cascade: [exact, graph, fts]}} + - {id: policy_gate, kind: policy, policy_ref: knowledge-policy.yml} + - {id: context_pack, kind: synthesizer, output_contract: schema/context-pack.schema.json} + - {id: enrich, kind: agent, agent_ref: enrich, model_policy_ref: openrouter:claude-sonnet-4-6} + - {id: export, kind: system_node} + - {id: finalize, kind: finalizer} +edges: + - {id: e_ingest_export, source: ingest, target: export, mode: normal} + - {id: e_retrieve_policy, source: retrieve, target: policy_gate, mode: normal} + - {id: e_policy_context, source: policy_gate, target: context_pack, mode: conditional} + - {id: e_context_finalize, source: context_pack, target: finalize, mode: normal} + - {id: e_finalize_end, source: finalize, target: END, mode: terminal} +risk_policy: + authority_model: [A0, A1, A2, A3, A4, A5] + default: deny diff --git a/agent_core/contracts/graphs/noc-agent.draft.graph.yaml b/agent_core/contracts/graphs/noc-agent.draft.graph.yaml new file mode 100644 index 0000000..8902d76 --- /dev/null +++ b/agent_core/contracts/graphs/noc-agent.draft.graph.yaml @@ -0,0 +1,46 @@ +# DESCRIPTIVE draft GraphSpec of the NOC agent's CURRENT LangGraph topology. +# Source of truth: hyrule-noc-agent/app/graph/graph.py + app/graph/nodes.py. +# Descriptive only — no compiler in this milestone. +schema_version: "0.1.0" +graph_id: noc-agent +version_name: draft-from-graph.py +description: >- + 13-node NOC investigation graph: correlate/dedupe -> recall history -> + supervisor route -> {bgp|firewall|infrastructure} specialist -> + evidence validation -> golden-state drift -> proposal -> approval interrupt -> + execute remediation -> verify. +owner: AS215932/noc-agent +entrypoint: correlate_and_dedupe +state_schema: app.graph.state.WorkflowState +nodes: + - {id: correlate_and_dedupe, kind: system_node} + - {id: recall_history, kind: memory, memory_namespaces: [cases, traces]} + - {id: supervisor_route, kind: system_node} + - {id: bgp_specialist, kind: agent, agent_ref: bgp_specialist, mcp_bindings: [hyrule-mcp]} + - {id: firewall_specialist, kind: agent, agent_ref: firewall_specialist, mcp_bindings: [hyrule-mcp]} + - {id: infrastructure_specialist, kind: agent, agent_ref: infrastructure_specialist, mcp_bindings: [hyrule-mcp]} + - {id: evidence_validation, kind: policy} + - {id: golden_state_drift_check, kind: policy} + - {id: proposal_build, kind: synthesizer} + - {id: prepare_approval, kind: system_node} + - {id: approval_interrupt, kind: human_approval} + - {id: execute_approved_remediation, kind: tool_executor, risk_tier: critical, mcp_bindings: [hyrule-mcp]} + - {id: verify_remediation, kind: finalizer} +edges: + - {id: e_corr_recall, source: correlate_and_dedupe, target: recall_history, mode: normal} + - {id: e_recall_route, source: recall_history, target: supervisor_route, mode: normal} + - {id: e_route_specialists, source: supervisor_route, target: bgp_specialist, mode: conditional, candidates: [bgp_specialist, firewall_specialist, infrastructure_specialist]} + - {id: e_bgp_validate, source: bgp_specialist, target: evidence_validation, mode: normal} + - {id: e_fw_validate, source: firewall_specialist, target: evidence_validation, mode: normal} + - {id: e_infra_validate, source: infrastructure_specialist, target: evidence_validation, mode: normal} + - {id: e_validate_drift, source: evidence_validation, target: golden_state_drift_check, mode: normal} + - {id: e_drift_proposal, source: golden_state_drift_check, target: proposal_build, mode: normal} + - {id: e_proposal_prepare, source: proposal_build, target: prepare_approval, mode: normal} + - {id: e_prepare_interrupt, source: prepare_approval, target: approval_interrupt, mode: interrupt} + - {id: e_interrupt_execute, source: approval_interrupt, target: execute_approved_remediation, mode: approval, candidates: [execute_approved_remediation, END]} + - {id: e_interrupt_end, source: approval_interrupt, target: END, mode: conditional} + - {id: e_execute_verify, source: execute_approved_remediation, target: verify_remediation, mode: normal} + - {id: e_verify_end, source: verify_remediation, target: END, mode: terminal} +risk_policy: + approval_required_for: [execute_approved_remediation] + invariant: no-false-all-clear # resolve never from absence (#31) diff --git a/agent_core/contracts/models.py b/agent_core/contracts/models.py new file mode 100644 index 0000000..8937ea5 --- /dev/null +++ b/agent_core/contracts/models.py @@ -0,0 +1,63 @@ +"""Model/provider contracts: cost/usage, model policy, routing decision.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import ( + DifficultyBand, + RiskLevel, + TraceableModel, + VersionedModel, +) + + +class CostUsage(VersionedModel): + """Token/latency/cost metadata for a single model call or run. + + All fields optional: engineering-loop reports USD (``CostReport``) but not + latency; NOC reports latency/fallback metrics but not USD. + """ + + model: str | None = None + provider: str | None = None + input_tokens: int | None = None + output_tokens: int | None = None + total_tokens: int | None = None + usd: float | None = None + latency_ms: float | None = None + fallback_used: bool = False + + +class ModelPolicy(VersionedModel): + """Provider-agnostic model selection policy (draft). + + Superset of engineering-loop ``model-policy.yml`` (role x tier + risk/retry + escalation) and NOC's ``FallbackModel`` chain. + """ + + policy_ref: str + role: str | None = None + allowed_providers: list[str] = Field(default_factory=list) + tiers: dict[str, Any] = Field(default_factory=dict) + risk_overrides: dict[str, Any] = Field(default_factory=dict) + retry_escalation: dict[str, Any] = Field(default_factory=dict) + fallback: list[str] = Field(default_factory=list) + objective_weights: dict[str, float] = Field(default_factory=dict) + + +class RoutingDecision(TraceableModel): + """Record of a model/router selection for one run (for scorecards later).""" + + agent_role: str | None = None + task_class: str | None = None + difficulty_band: DifficultyBand | None = None + risk_level: RiskLevel = "low" + selected_model: str + selected_provider: str | None = None + candidate_models: list[str] = Field(default_factory=list) + router_policy_ref: str | None = None + scores: dict[str, float] = Field(default_factory=dict) + exploration: bool = False diff --git a/agent_core/contracts/schemas/AgentState.schema.json b/agent_core/contracts/schemas/AgentState.schema.json new file mode 100644 index 0000000..d4c351c --- /dev/null +++ b/agent_core/contracts/schemas/AgentState.schema.json @@ -0,0 +1,325 @@ +{ + "$defs": { + "TaskEnvelope": { + "additionalProperties": false, + "description": "Standard task input.\n\nMaps from engineering-loop (``change_id``/``change_class``/``risk_level``/\n``source_of_truth_files``), NOC (``incident_id`` + ``normalized_alert`` +\n``resource_id``), and knowledge (``task``/``role``/``risk_level``).", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "customer_impact": { + "anyOf": [ + { + "enum": [ + "none", + "possible", + "expected" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Customer Impact" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "input": { + "additionalProperties": true, + "title": "Input", + "type": "object" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "requested_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Requested By" + }, + "risk_level": { + "default": "low", + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "title": "Risk Level", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "task_class": { + "title": "Task Class", + "type": "string" + }, + "task_id": { + "title": "Task Id", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "task_id", + "task_class", + "source" + ], + "title": "TaskEnvelope", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Minimal, serializable view of an agent run's state.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "decision_refs": { + "items": { + "type": "string" + }, + "title": "Decision Refs", + "type": "array" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "evidence_refs": { + "items": { + "type": "string" + }, + "title": "Evidence Refs", + "type": "array" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "messages": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Messages", + "type": "array" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "retries": { + "additionalProperties": { + "type": "integer" + }, + "title": "Retries", + "type": "object" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "scratch": { + "additionalProperties": true, + "title": "Scratch", + "type": "object" + }, + "status": { + "default": "pending", + "title": "Status", + "type": "string" + }, + "task": { + "anyOf": [ + { + "$ref": "#/$defs/TaskEnvelope" + }, + { + "type": "null" + } + ], + "default": null + }, + "trace_event_refs": { + "items": { + "type": "string" + }, + "title": "Trace Event Refs", + "type": "array" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "title": "AgentState", + "type": "object" +} diff --git a/agent_core/contracts/schemas/AuditEvent.schema.json b/agent_core/contracts/schemas/AuditEvent.schema.json new file mode 100644 index 0000000..fdc79ff --- /dev/null +++ b/agent_core/contracts/schemas/AuditEvent.schema.json @@ -0,0 +1,66 @@ +{ + "additionalProperties": false, + "description": "Audit record for a control-plane change (graph edit, promotion, etc.).", + "properties": { + "action": { + "title": "Action", + "type": "string" + }, + "actor_id": { + "title": "Actor Id", + "type": "string" + }, + "after": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After" + }, + "before": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before" + }, + "created_at": { + "format": "date-time", + "title": "Created 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": [ + "actor_id", + "action", + "target_type", + "target_id" + ], + "title": "AuditEvent", + "type": "object" +} diff --git a/agent_core/contracts/schemas/CostUsage.schema.json b/agent_core/contracts/schemas/CostUsage.schema.json new file mode 100644 index 0000000..1a50dfb --- /dev/null +++ b/agent_core/contracts/schemas/CostUsage.schema.json @@ -0,0 +1,102 @@ +{ + "additionalProperties": false, + "description": "Token/latency/cost metadata for a single model call or run.\n\nAll fields optional: engineering-loop reports USD (``CostReport``) but not\nlatency; NOC reports latency/fallback metrics but not USD.", + "properties": { + "fallback_used": { + "default": false, + "title": "Fallback Used", + "type": "boolean" + }, + "input_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model" + }, + "output_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "total_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Tokens" + }, + "usd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Usd" + } + }, + "title": "CostUsage", + "type": "object" +} diff --git a/agent_core/contracts/schemas/DecisionPacket.schema.json b/agent_core/contracts/schemas/DecisionPacket.schema.json new file mode 100644 index 0000000..6ee6861 --- /dev/null +++ b/agent_core/contracts/schemas/DecisionPacket.schema.json @@ -0,0 +1,769 @@ +{ + "$defs": { + "CostUsage": { + "additionalProperties": false, + "description": "Token/latency/cost metadata for a single model call or run.\n\nAll fields optional: engineering-loop reports USD (``CostReport``) but not\nlatency; NOC reports latency/fallback metrics but not USD.", + "properties": { + "fallback_used": { + "default": false, + "title": "Fallback Used", + "type": "boolean" + }, + "input_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model" + }, + "output_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "total_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Tokens" + }, + "usd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Usd" + } + }, + "title": "CostUsage", + "type": "object" + }, + "ErrorEnvelope": { + "additionalProperties": false, + "description": "Structured, serializable error shape.\n\nMaps from NOC ``SafeError(category, public_message, operator_next_steps)`` and\nengineering-loop ``*Error`` RuntimeError subclasses.", + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Category" + }, + "details": { + "additionalProperties": true, + "title": "Details", + "type": "object" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "title": "Occurred At", + "type": "string" + }, + "operator_next_steps": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operator Next Steps" + }, + "public_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Public Message" + }, + "retryable": { + "default": false, + "title": "Retryable", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "error_type", + "message" + ], + "title": "ErrorEnvelope", + "type": "object" + }, + "EvidencePacket": { + "additionalProperties": false, + "description": "Sources + tool outputs + references that ground a decision/answer.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "authority_max": { + "anyOf": [ + { + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Authority Max" + }, + "code_refs": { + "items": { + "type": "string" + }, + "title": "Code Refs", + "type": "array" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "logs": { + "items": { + "type": "string" + }, + "title": "Logs", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "sources": { + "items": { + "$ref": "#/$defs/SourceRef" + }, + "title": "Sources", + "type": "array" + }, + "tool_results": { + "items": { + "$ref": "#/$defs/ToolResult" + }, + "title": "Tool Results", + "type": "array" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + }, + "unresolved_questions": { + "items": { + "type": "string" + }, + "title": "Unresolved Questions", + "type": "array" + } + }, + "title": "EvidencePacket", + "type": "object" + }, + "SourceRef": { + "additionalProperties": false, + "description": "A cited source with optional A0-A5 authority (from the knowledge loop).", + "properties": { + "authority": { + "anyOf": [ + { + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Authority" + }, + "commit_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Commit Sha" + }, + "excerpt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Excerpt" + }, + "kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Kind" + }, + "ref": { + "title": "Ref", + "type": "string" + }, + "review_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Review Status" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "ref" + ], + "title": "SourceRef", + "type": "object" + }, + "ToolResult": { + "additionalProperties": false, + "description": "Standard envelope for the result of any tool/MCP invocation.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "approval_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approval Decision" + }, + "cost": { + "anyOf": [ + { + "$ref": "#/$defs/CostUsage" + }, + { + "type": "null" + } + ], + "default": null + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "error": { + "anyOf": [ + { + "$ref": "#/$defs/ErrorEnvelope" + }, + { + "type": "null" + } + ], + "default": null + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "ok": { + "default": true, + "title": "Ok", + "type": "boolean" + }, + "output": { + "additionalProperties": true, + "title": "Output", + "type": "object" + }, + "permission_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Permission Decision" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "tool" + ], + "title": "ToolResult", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Standard agent decision output.\n\nMaps from engineering-loop ``RoleReviewOutput`` (approved/notes/mutations) and\nNOC ``ChangeProposal`` (assessment/root_cause/confidence/structured_actions).", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "approved": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approved" + }, + "confidence": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Confidence" + }, + "decision": { + "title": "Decision", + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "evidence": { + "anyOf": [ + { + "$ref": "#/$defs/EvidencePacket" + }, + { + "type": "null" + } + ], + "default": null + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "next_action": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Next Action" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "proposed_actions": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Proposed Actions", + "type": "array" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + }, + "validation_errors": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Validation Errors", + "type": "array" + } + }, + "required": [ + "decision" + ], + "title": "DecisionPacket", + "type": "object" +} diff --git a/agent_core/contracts/schemas/EdgeSpec.schema.json b/agent_core/contracts/schemas/EdgeSpec.schema.json new file mode 100644 index 0000000..2d46be0 --- /dev/null +++ b/agent_core/contracts/schemas/EdgeSpec.schema.json @@ -0,0 +1,131 @@ +{ + "additionalProperties": false, + "description": "Declarative edge spec.", + "properties": { + "candidates": { + "items": { + "type": "string" + }, + "title": "Candidates", + "type": "array" + }, + "condition": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Condition" + }, + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" + }, + "guardrails": { + "additionalProperties": true, + "title": "Guardrails", + "type": "object" + }, + "id": { + "title": "Id", + "type": "string" + }, + "max_attempts": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Attempts" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "mode": { + "default": "normal", + "enum": [ + "normal", + "conditional", + "parallel", + "fallback", + "retry", + "approval", + "interrupt", + "terminal" + ], + "title": "Mode", + "type": "string" + }, + "router_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Router Ref" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "target": { + "title": "Target", + "type": "string" + }, + "timeout_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout Seconds" + }, + "weight": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Weight" + } + }, + "required": [ + "id", + "source", + "target" + ], + "title": "EdgeSpec", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ErrorEnvelope.schema.json b/agent_core/contracts/schemas/ErrorEnvelope.schema.json new file mode 100644 index 0000000..fb96f8f --- /dev/null +++ b/agent_core/contracts/schemas/ErrorEnvelope.schema.json @@ -0,0 +1,76 @@ +{ + "additionalProperties": false, + "description": "Structured, serializable error shape.\n\nMaps from NOC ``SafeError(category, public_message, operator_next_steps)`` and\nengineering-loop ``*Error`` RuntimeError subclasses.", + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Category" + }, + "details": { + "additionalProperties": true, + "title": "Details", + "type": "object" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "title": "Occurred At", + "type": "string" + }, + "operator_next_steps": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operator Next Steps" + }, + "public_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Public Message" + }, + "retryable": { + "default": false, + "title": "Retryable", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "error_type", + "message" + ], + "title": "ErrorEnvelope", + "type": "object" +} diff --git a/agent_core/contracts/schemas/EvidencePacket.schema.json b/agent_core/contracts/schemas/EvidencePacket.schema.json new file mode 100644 index 0000000..02ecfac --- /dev/null +++ b/agent_core/contracts/schemas/EvidencePacket.schema.json @@ -0,0 +1,597 @@ +{ + "$defs": { + "CostUsage": { + "additionalProperties": false, + "description": "Token/latency/cost metadata for a single model call or run.\n\nAll fields optional: engineering-loop reports USD (``CostReport``) but not\nlatency; NOC reports latency/fallback metrics but not USD.", + "properties": { + "fallback_used": { + "default": false, + "title": "Fallback Used", + "type": "boolean" + }, + "input_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model" + }, + "output_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "total_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Tokens" + }, + "usd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Usd" + } + }, + "title": "CostUsage", + "type": "object" + }, + "ErrorEnvelope": { + "additionalProperties": false, + "description": "Structured, serializable error shape.\n\nMaps from NOC ``SafeError(category, public_message, operator_next_steps)`` and\nengineering-loop ``*Error`` RuntimeError subclasses.", + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Category" + }, + "details": { + "additionalProperties": true, + "title": "Details", + "type": "object" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "title": "Occurred At", + "type": "string" + }, + "operator_next_steps": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operator Next Steps" + }, + "public_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Public Message" + }, + "retryable": { + "default": false, + "title": "Retryable", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "error_type", + "message" + ], + "title": "ErrorEnvelope", + "type": "object" + }, + "SourceRef": { + "additionalProperties": false, + "description": "A cited source with optional A0-A5 authority (from the knowledge loop).", + "properties": { + "authority": { + "anyOf": [ + { + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Authority" + }, + "commit_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Commit Sha" + }, + "excerpt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Excerpt" + }, + "kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Kind" + }, + "ref": { + "title": "Ref", + "type": "string" + }, + "review_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Review Status" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "ref" + ], + "title": "SourceRef", + "type": "object" + }, + "ToolResult": { + "additionalProperties": false, + "description": "Standard envelope for the result of any tool/MCP invocation.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "approval_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approval Decision" + }, + "cost": { + "anyOf": [ + { + "$ref": "#/$defs/CostUsage" + }, + { + "type": "null" + } + ], + "default": null + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "error": { + "anyOf": [ + { + "$ref": "#/$defs/ErrorEnvelope" + }, + { + "type": "null" + } + ], + "default": null + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "ok": { + "default": true, + "title": "Ok", + "type": "boolean" + }, + "output": { + "additionalProperties": true, + "title": "Output", + "type": "object" + }, + "permission_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Permission Decision" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "tool" + ], + "title": "ToolResult", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Sources + tool outputs + references that ground a decision/answer.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "authority_max": { + "anyOf": [ + { + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Authority Max" + }, + "code_refs": { + "items": { + "type": "string" + }, + "title": "Code Refs", + "type": "array" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "logs": { + "items": { + "type": "string" + }, + "title": "Logs", + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "sources": { + "items": { + "$ref": "#/$defs/SourceRef" + }, + "title": "Sources", + "type": "array" + }, + "tool_results": { + "items": { + "$ref": "#/$defs/ToolResult" + }, + "title": "Tool Results", + "type": "array" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + }, + "unresolved_questions": { + "items": { + "type": "string" + }, + "title": "Unresolved Questions", + "type": "array" + } + }, + "title": "EvidencePacket", + "type": "object" +} diff --git a/agent_core/contracts/schemas/FeedbackEvent.schema.json b/agent_core/contracts/schemas/FeedbackEvent.schema.json new file mode 100644 index 0000000..5405457 --- /dev/null +++ b/agent_core/contracts/schemas/FeedbackEvent.schema.json @@ -0,0 +1,198 @@ +{ + "additionalProperties": false, + "description": "Raw feedback on an answer/run/node/model.\n\nFirst source: NOC ``operator_feedback`` (recorded today, unused downstream) and\nengineering-loop curated-lesson proposals. Normalizes later into RewardSignal.", + "properties": { + "actor_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Actor Id" + }, + "actor_role": { + "default": "operator", + "enum": [ + "end_user", + "operator", + "senior", + "system" + ], + "title": "Actor Role", + "type": "string" + }, + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "categories": { + "items": { + "type": "string" + }, + "title": "Categories", + "type": "array" + }, + "correction_text": { + "default": "", + "title": "Correction Text", + "type": "string" + }, + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "feedback_id": { + "title": "Feedback Id", + "type": "string" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "promote_to_eval": { + "default": false, + "title": "Promote To Eval", + "type": "boolean" + }, + "promote_to_judge_alignment": { + "default": false, + "title": "Promote To Judge Alignment", + "type": "boolean" + }, + "promote_to_memory": { + "default": false, + "title": "Promote To Memory", + "type": "boolean" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "score": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Score" + }, + "signal_type": { + "default": "good", + "title": "Signal Type", + "type": "string" + }, + "target_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Target Id" + }, + "target_type": { + "default": "answer", + "title": "Target Type", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "feedback_id" + ], + "title": "FeedbackEvent", + "type": "object" +} diff --git a/agent_core/contracts/schemas/GraphSpec.schema.json b/agent_core/contracts/schemas/GraphSpec.schema.json new file mode 100644 index 0000000..52c9be0 --- /dev/null +++ b/agent_core/contracts/schemas/GraphSpec.schema.json @@ -0,0 +1,462 @@ +{ + "$defs": { + "EdgeSpec": { + "additionalProperties": false, + "description": "Declarative edge spec.", + "properties": { + "candidates": { + "items": { + "type": "string" + }, + "title": "Candidates", + "type": "array" + }, + "condition": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Condition" + }, + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" + }, + "guardrails": { + "additionalProperties": true, + "title": "Guardrails", + "type": "object" + }, + "id": { + "title": "Id", + "type": "string" + }, + "max_attempts": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Max Attempts" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "mode": { + "default": "normal", + "enum": [ + "normal", + "conditional", + "parallel", + "fallback", + "retry", + "approval", + "interrupt", + "terminal" + ], + "title": "Mode", + "type": "string" + }, + "router_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Router Ref" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "target": { + "title": "Target", + "type": "string" + }, + "timeout_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout Seconds" + }, + "weight": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Weight" + } + }, + "required": [ + "id", + "source", + "target" + ], + "title": "EdgeSpec", + "type": "object" + }, + "NodeSpec": { + "additionalProperties": false, + "description": "Declarative node spec.", + "properties": { + "agent_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Ref" + }, + "config": { + "additionalProperties": true, + "title": "Config", + "type": "object" + }, + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" + }, + "id": { + "title": "Id", + "type": "string" + }, + "implementation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Implementation" + }, + "input_contract": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Contract" + }, + "judge_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Judge Ref" + }, + "kind": { + "enum": [ + "agent", + "judge", + "tool_executor", + "mcp", + "retriever", + "memory", + "policy", + "human_approval", + "subgraph", + "parallel_council", + "synthesizer", + "system_node", + "finalizer" + ], + "title": "Kind", + "type": "string" + }, + "mcp_bindings": { + "items": { + "type": "string" + }, + "title": "Mcp Bindings", + "type": "array" + }, + "memory_namespaces": { + "items": { + "type": "string" + }, + "title": "Memory Namespaces", + "type": "array" + }, + "model_policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Policy Ref" + }, + "output_contract": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Contract" + }, + "policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Policy Ref" + }, + "retry_policy": { + "additionalProperties": true, + "title": "Retry Policy", + "type": "object" + }, + "risk_tier": { + "anyOf": [ + { + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Risk Tier" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "subgraph_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Subgraph Ref" + }, + "timeout_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout Seconds" + }, + "tool_bindings": { + "items": { + "type": "string" + }, + "title": "Tool Bindings", + "type": "array" + } + }, + "required": [ + "id", + "kind" + ], + "title": "NodeSpec", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Declarative graph topology spec (descriptive in this milestone).", + "properties": { + "deployment": { + "additionalProperties": true, + "title": "Deployment", + "type": "object" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "edges": { + "items": { + "$ref": "#/$defs/EdgeSpec" + }, + "title": "Edges", + "type": "array" + }, + "entrypoint": { + "title": "Entrypoint", + "type": "string" + }, + "graph_id": { + "title": "Graph Id", + "type": "string" + }, + "judge_policies": { + "additionalProperties": true, + "title": "Judge Policies", + "type": "object" + }, + "learning_config": { + "additionalProperties": true, + "title": "Learning Config", + "type": "object" + }, + "mcp_bindings": { + "additionalProperties": true, + "title": "Mcp Bindings", + "type": "object" + }, + "model_policies": { + "additionalProperties": true, + "title": "Model Policies", + "type": "object" + }, + "nodes": { + "items": { + "$ref": "#/$defs/NodeSpec" + }, + "title": "Nodes", + "type": "array" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "risk_policy": { + "additionalProperties": true, + "title": "Risk Policy", + "type": "object" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "state_schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "State Schema" + }, + "subgraphs": { + "items": { + "type": "string" + }, + "title": "Subgraphs", + "type": "array" + }, + "task_schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Task Schema" + }, + "tool_bindings": { + "additionalProperties": true, + "title": "Tool Bindings", + "type": "object" + }, + "version_name": { + "title": "Version Name", + "type": "string" + } + }, + "required": [ + "graph_id", + "version_name", + "entrypoint" + ], + "title": "GraphSpec", + "type": "object" +} diff --git a/agent_core/contracts/schemas/HumanApprovalDecision.schema.json b/agent_core/contracts/schemas/HumanApprovalDecision.schema.json new file mode 100644 index 0000000..50bdeb8 --- /dev/null +++ b/agent_core/contracts/schemas/HumanApprovalDecision.schema.json @@ -0,0 +1,141 @@ +{ + "additionalProperties": false, + "description": "An operator's approval/rejection. Maps from NOC ``ApprovalDecision``.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "approver": { + "title": "Approver", + "type": "string" + }, + "approver_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approver Role" + }, + "decided_at": { + "format": "date-time", + "title": "Decided At", + "type": "string" + }, + "decision": { + "enum": [ + "approved", + "rejected", + "acknowledged" + ], + "title": "Decision", + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "request_id": { + "title": "Request Id", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "request_id", + "decision", + "approver" + ], + "title": "HumanApprovalDecision", + "type": "object" +} diff --git a/agent_core/contracts/schemas/HumanApprovalRequest.schema.json b/agent_core/contracts/schemas/HumanApprovalRequest.schema.json new file mode 100644 index 0000000..2434c49 --- /dev/null +++ b/agent_core/contracts/schemas/HumanApprovalRequest.schema.json @@ -0,0 +1,155 @@ +{ + "additionalProperties": false, + "description": "A pause/approval request raised at a graph interrupt.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "expires_at": { + "anyOf": [ + { + "format": "date-time", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires At" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "payload": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "request_id": { + "title": "Request Id", + "type": "string" + }, + "required_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Required Role" + }, + "risk_level": { + "default": "low", + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "title": "Risk Level", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "request_id", + "reason" + ], + "title": "HumanApprovalRequest", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ModelPolicy.schema.json b/agent_core/contracts/schemas/ModelPolicy.schema.json new file mode 100644 index 0000000..e16ebf6 --- /dev/null +++ b/agent_core/contracts/schemas/ModelPolicy.schema.json @@ -0,0 +1,68 @@ +{ + "additionalProperties": false, + "description": "Provider-agnostic model selection policy (draft).\n\nSuperset of engineering-loop ``model-policy.yml`` (role x tier + risk/retry\nescalation) and NOC's ``FallbackModel`` chain.", + "properties": { + "allowed_providers": { + "items": { + "type": "string" + }, + "title": "Allowed Providers", + "type": "array" + }, + "fallback": { + "items": { + "type": "string" + }, + "title": "Fallback", + "type": "array" + }, + "objective_weights": { + "additionalProperties": { + "type": "number" + }, + "title": "Objective Weights", + "type": "object" + }, + "policy_ref": { + "title": "Policy Ref", + "type": "string" + }, + "retry_escalation": { + "additionalProperties": true, + "title": "Retry Escalation", + "type": "object" + }, + "risk_overrides": { + "additionalProperties": true, + "title": "Risk Overrides", + "type": "object" + }, + "role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Role" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "tiers": { + "additionalProperties": true, + "title": "Tiers", + "type": "object" + } + }, + "required": [ + "policy_ref" + ], + "title": "ModelPolicy", + "type": "object" +} diff --git a/agent_core/contracts/schemas/NodeSpec.schema.json b/agent_core/contracts/schemas/NodeSpec.schema.json new file mode 100644 index 0000000..3792349 --- /dev/null +++ b/agent_core/contracts/schemas/NodeSpec.schema.json @@ -0,0 +1,202 @@ +{ + "additionalProperties": false, + "description": "Declarative node spec.", + "properties": { + "agent_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Ref" + }, + "config": { + "additionalProperties": true, + "title": "Config", + "type": "object" + }, + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" + }, + "id": { + "title": "Id", + "type": "string" + }, + "implementation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Implementation" + }, + "input_contract": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Contract" + }, + "judge_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Judge Ref" + }, + "kind": { + "enum": [ + "agent", + "judge", + "tool_executor", + "mcp", + "retriever", + "memory", + "policy", + "human_approval", + "subgraph", + "parallel_council", + "synthesizer", + "system_node", + "finalizer" + ], + "title": "Kind", + "type": "string" + }, + "mcp_bindings": { + "items": { + "type": "string" + }, + "title": "Mcp Bindings", + "type": "array" + }, + "memory_namespaces": { + "items": { + "type": "string" + }, + "title": "Memory Namespaces", + "type": "array" + }, + "model_policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Policy Ref" + }, + "output_contract": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Contract" + }, + "policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Policy Ref" + }, + "retry_policy": { + "additionalProperties": true, + "title": "Retry Policy", + "type": "object" + }, + "risk_tier": { + "anyOf": [ + { + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Risk Tier" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "subgraph_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Subgraph Ref" + }, + "timeout_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout Seconds" + }, + "tool_bindings": { + "items": { + "type": "string" + }, + "title": "Tool Bindings", + "type": "array" + } + }, + "required": [ + "id", + "kind" + ], + "title": "NodeSpec", + "type": "object" +} diff --git a/agent_core/contracts/schemas/PolicyGateResult.schema.json b/agent_core/contracts/schemas/PolicyGateResult.schema.json new file mode 100644 index 0000000..fbcdfa0 --- /dev/null +++ b/agent_core/contracts/schemas/PolicyGateResult.schema.json @@ -0,0 +1,139 @@ +{ + "additionalProperties": false, + "description": "Structured safety/approval gate decision.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "allow": { + "title": "Allow", + "type": "boolean" + }, + "audit": { + "additionalProperties": true, + "title": "Audit", + "type": "object" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "reason": { + "default": "", + "title": "Reason", + "type": "string" + }, + "require_human_approval": { + "default": false, + "title": "Require Human Approval", + "type": "boolean" + }, + "require_more_evidence": { + "default": false, + "title": "Require More Evidence", + "type": "boolean" + }, + "require_runbook": { + "default": false, + "title": "Require Runbook", + "type": "boolean" + }, + "require_stronger_judge": { + "default": false, + "title": "Require Stronger Judge", + "type": "boolean" + }, + "require_ticket_link": { + "default": false, + "title": "Require Ticket Link", + "type": "boolean" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "allow" + ], + "title": "PolicyGateResult", + "type": "object" +} diff --git a/agent_core/contracts/schemas/RoutingDecision.schema.json b/agent_core/contracts/schemas/RoutingDecision.schema.json new file mode 100644 index 0000000..c3aaff4 --- /dev/null +++ b/agent_core/contracts/schemas/RoutingDecision.schema.json @@ -0,0 +1,188 @@ +{ + "additionalProperties": false, + "description": "Record of a model/router selection for one run (for scorecards later).", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "candidate_models": { + "items": { + "type": "string" + }, + "title": "Candidate Models", + "type": "array" + }, + "difficulty_band": { + "anyOf": [ + { + "enum": [ + "cheap", + "balanced", + "strong", + "frontier" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Difficulty Band" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "exploration": { + "default": false, + "title": "Exploration", + "type": "boolean" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "risk_level": { + "default": "low", + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "title": "Risk Level", + "type": "string" + }, + "router_policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Router Policy Ref" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "scores": { + "additionalProperties": { + "type": "number" + }, + "title": "Scores", + "type": "object" + }, + "selected_model": { + "title": "Selected Model", + "type": "string" + }, + "selected_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Selected Provider" + }, + "task_class": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Task Class" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "selected_model" + ], + "title": "RoutingDecision", + "type": "object" +} diff --git a/agent_core/contracts/schemas/RunContext.schema.json b/agent_core/contracts/schemas/RunContext.schema.json new file mode 100644 index 0000000..2f76cec --- /dev/null +++ b/agent_core/contracts/schemas/RunContext.schema.json @@ -0,0 +1,138 @@ +{ + "additionalProperties": false, + "description": "Runtime metadata for a single run; the provenance carrier.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "deployment_slot": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deployment Slot" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "instruction_pack_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Instruction Pack Ref" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "model_policy_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model Policy Ref" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "started_at": { + "format": "date-time", + "title": "Started At", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "run_id" + ], + "title": "RunContext", + "type": "object" +} diff --git a/agent_core/contracts/schemas/RuntimeContext.schema.json b/agent_core/contracts/schemas/RuntimeContext.schema.json new file mode 100644 index 0000000..570d965 --- /dev/null +++ b/agent_core/contracts/schemas/RuntimeContext.schema.json @@ -0,0 +1,42 @@ +{ + "additionalProperties": false, + "description": "Environment-level runtime dependencies/config (not per-run).", + "properties": { + "config": { + "additionalProperties": true, + "title": "Config", + "type": "object" + }, + "deployment_slot": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Deployment Slot" + }, + "environment": { + "default": "dev", + "title": "Environment", + "type": "string" + }, + "feature_flags": { + "additionalProperties": { + "type": "boolean" + }, + "title": "Feature Flags", + "type": "object" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "title": "RuntimeContext", + "type": "object" +} diff --git a/agent_core/contracts/schemas/SourceRef.schema.json b/agent_core/contracts/schemas/SourceRef.schema.json new file mode 100644 index 0000000..e4a535e --- /dev/null +++ b/agent_core/contracts/schemas/SourceRef.schema.json @@ -0,0 +1,88 @@ +{ + "additionalProperties": false, + "description": "A cited source with optional A0-A5 authority (from the knowledge loop).", + "properties": { + "authority": { + "anyOf": [ + { + "enum": [ + "A0", + "A1", + "A2", + "A3", + "A4", + "A5" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Authority" + }, + "commit_sha": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Commit Sha" + }, + "excerpt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Excerpt" + }, + "kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Kind" + }, + "ref": { + "title": "Ref", + "type": "string" + }, + "review_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Review Status" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "ref" + ], + "title": "SourceRef", + "type": "object" +} diff --git a/agent_core/contracts/schemas/TaskEnvelope.schema.json b/agent_core/contracts/schemas/TaskEnvelope.schema.json new file mode 100644 index 0000000..954bd90 --- /dev/null +++ b/agent_core/contracts/schemas/TaskEnvelope.schema.json @@ -0,0 +1,169 @@ +{ + "additionalProperties": false, + "description": "Standard task input.\n\nMaps from engineering-loop (``change_id``/``change_class``/``risk_level``/\n``source_of_truth_files``), NOC (``incident_id`` + ``normalized_alert`` +\n``resource_id``), and knowledge (``task``/``role``/``risk_level``).", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "created_at": { + "format": "date-time", + "title": "Created At", + "type": "string" + }, + "customer_impact": { + "anyOf": [ + { + "enum": [ + "none", + "possible", + "expected" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Customer Impact" + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "input": { + "additionalProperties": true, + "title": "Input", + "type": "object" + }, + "metadata": { + "additionalProperties": true, + "title": "Metadata", + "type": "object" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "requested_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Requested By" + }, + "risk_level": { + "default": "low", + "enum": [ + "low", + "medium", + "high", + "critical" + ], + "title": "Risk Level", + "type": "string" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "task_class": { + "title": "Task Class", + "type": "string" + }, + "task_id": { + "title": "Task Id", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "task_id", + "task_class", + "source" + ], + "title": "TaskEnvelope", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ToolContract.schema.json b/agent_core/contracts/schemas/ToolContract.schema.json new file mode 100644 index 0000000..564d972 --- /dev/null +++ b/agent_core/contracts/schemas/ToolContract.schema.json @@ -0,0 +1,97 @@ +{ + "additionalProperties": false, + "description": "Typed declaration of a tool (or MCP tool) the runtime may execute.", + "properties": { + "audit_level": { + "default": "standard", + "title": "Audit Level", + "type": "string" + }, + "input_schema": { + "additionalProperties": true, + "title": "Input Schema", + "type": "object" + }, + "name": { + "title": "Name", + "type": "string" + }, + "output_schema": { + "additionalProperties": true, + "title": "Output Schema", + "type": "object" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "permissions": { + "additionalProperties": true, + "title": "Permissions", + "type": "object" + }, + "rate_limit": { + "additionalProperties": true, + "title": "Rate Limit", + "type": "object" + }, + "risk_tier": { + "default": "read_only", + "enum": [ + "read_only", + "low_write", + "high_write", + "prod_change" + ], + "title": "Risk Tier", + "type": "string" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "side_effects": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Side Effects" + }, + "timeout_seconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout Seconds" + }, + "version": { + "default": "1", + "title": "Version", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "ToolContract", + "type": "object" +} diff --git a/agent_core/contracts/schemas/ToolResult.schema.json b/agent_core/contracts/schemas/ToolResult.schema.json new file mode 100644 index 0000000..74ba524 --- /dev/null +++ b/agent_core/contracts/schemas/ToolResult.schema.json @@ -0,0 +1,352 @@ +{ + "$defs": { + "CostUsage": { + "additionalProperties": false, + "description": "Token/latency/cost metadata for a single model call or run.\n\nAll fields optional: engineering-loop reports USD (``CostReport``) but not\nlatency; NOC reports latency/fallback metrics but not USD.", + "properties": { + "fallback_used": { + "default": false, + "title": "Fallback Used", + "type": "boolean" + }, + "input_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model" + }, + "output_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "total_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Tokens" + }, + "usd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Usd" + } + }, + "title": "CostUsage", + "type": "object" + }, + "ErrorEnvelope": { + "additionalProperties": false, + "description": "Structured, serializable error shape.\n\nMaps from NOC ``SafeError(category, public_message, operator_next_steps)`` and\nengineering-loop ``*Error`` RuntimeError subclasses.", + "properties": { + "category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Category" + }, + "details": { + "additionalProperties": true, + "title": "Details", + "type": "object" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "title": "Occurred At", + "type": "string" + }, + "operator_next_steps": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operator Next Steps" + }, + "public_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Public Message" + }, + "retryable": { + "default": false, + "title": "Retryable", + "type": "boolean" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + } + }, + "required": [ + "error_type", + "message" + ], + "title": "ErrorEnvelope", + "type": "object" + } + }, + "additionalProperties": false, + "description": "Standard envelope for the result of any tool/MCP invocation.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "approval_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approval Decision" + }, + "cost": { + "anyOf": [ + { + "$ref": "#/$defs/CostUsage" + }, + { + "type": "null" + } + ], + "default": null + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "error": { + "anyOf": [ + { + "$ref": "#/$defs/ErrorEnvelope" + }, + { + "type": "null" + } + ], + "default": null + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "ok": { + "default": true, + "title": "Ok", + "type": "boolean" + }, + "output": { + "additionalProperties": true, + "title": "Output", + "type": "object" + }, + "permission_decision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Permission Decision" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "tool" + ], + "title": "ToolResult", + "type": "object" +} diff --git a/agent_core/contracts/schemas/TraceEvent.schema.json b/agent_core/contracts/schemas/TraceEvent.schema.json new file mode 100644 index 0000000..11ad27a --- /dev/null +++ b/agent_core/contracts/schemas/TraceEvent.schema.json @@ -0,0 +1,238 @@ +{ + "$defs": { + "CostUsage": { + "additionalProperties": false, + "description": "Token/latency/cost metadata for a single model call or run.\n\nAll fields optional: engineering-loop reports USD (``CostReport``) but not\nlatency; NOC reports latency/fallback metrics but not USD.", + "properties": { + "fallback_used": { + "default": false, + "title": "Fallback Used", + "type": "boolean" + }, + "input_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Tokens" + }, + "latency_ms": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Latency Ms" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Model" + }, + "output_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Tokens" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "total_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Total Tokens" + }, + "usd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Usd" + } + }, + "title": "CostUsage", + "type": "object" + } + }, + "additionalProperties": false, + "description": "A single event emitted during execution.\n\nMaps from engineering-loop ``loop_trace.json`` per-node summaries and NOC\n``traces``/``CaseEvent`` rows.", + "properties": { + "agent_role": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Role" + }, + "cost": { + "anyOf": [ + { + "$ref": "#/$defs/CostUsage" + }, + { + "type": "null" + } + ], + "default": null + }, + "environment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Environment" + }, + "event_id": { + "title": "Event Id", + "type": "string" + }, + "event_type": { + "title": "Event Type", + "type": "string" + }, + "graph_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Id" + }, + "graph_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Graph Version" + }, + "node_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Node Id" + }, + "payload": { + "additionalProperties": true, + "title": "Payload", + "type": "object" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Run Id" + }, + "schema_version": { + "default": "0.1.0", + "title": "Schema Version", + "type": "string" + }, + "summary": { + "default": "", + "title": "Summary", + "type": "string" + }, + "timestamp": { + "format": "date-time", + "title": "Timestamp", + "type": "string" + }, + "trace_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Trace Id" + } + }, + "required": [ + "event_type" + ], + "title": "TraceEvent", + "type": "object" +} diff --git a/agent_core/contracts/state.py b/agent_core/contracts/state.py new file mode 100644 index 0000000..e9b71b6 --- /dev/null +++ b/agent_core/contracts/state.py @@ -0,0 +1,28 @@ +"""AgentState: a JSON-serializable contract anchor for loop state. + +This is the *contract* shape, not a replacement for a loop's live LangGraph state +(engineering-loop ``GraphState`` TypedDict + reducers, NOC ``WorkflowState``). +Adapters project a live state into this shape; nothing here replaces those types. +""" + +from __future__ import annotations + +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import TraceableModel +from agent_core.contracts.task import TaskEnvelope + + +class AgentState(TraceableModel): + """Minimal, serializable view of an agent run's state.""" + + task: TaskEnvelope | None = None + status: str = "pending" + messages: list[dict[str, Any]] = Field(default_factory=list) + scratch: dict[str, Any] = Field(default_factory=dict) + retries: dict[str, int] = Field(default_factory=dict) + evidence_refs: list[str] = Field(default_factory=list) + decision_refs: list[str] = Field(default_factory=list) + trace_event_refs: list[str] = Field(default_factory=list) diff --git a/agent_core/contracts/task.py b/agent_core/contracts/task.py new file mode 100644 index 0000000..a19fbaa --- /dev/null +++ b/agent_core/contracts/task.py @@ -0,0 +1,29 @@ +"""TaskEnvelope: the standard input wrapper for all agent tasks.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any, Literal + +from pydantic import Field + +from agent_core.contracts._base import RiskLevel, TraceableModel, utcnow + + +class TaskEnvelope(TraceableModel): + """Standard task input. + + Maps from engineering-loop (``change_id``/``change_class``/``risk_level``/ + ``source_of_truth_files``), NOC (``incident_id`` + ``normalized_alert`` + + ``resource_id``), and knowledge (``task``/``role``/``risk_level``). + """ + + task_id: str + task_class: str + source: str + risk_level: RiskLevel = "low" + customer_impact: Literal["none", "possible", "expected"] | None = None + input: dict[str, Any] = Field(default_factory=dict) + requested_by: str | None = None + created_at: datetime = Field(default_factory=utcnow) + metadata: dict[str, Any] = Field(default_factory=dict) diff --git a/agent_core/contracts/tools.py b/agent_core/contracts/tools.py new file mode 100644 index 0000000..b861c02 --- /dev/null +++ b/agent_core/contracts/tools.py @@ -0,0 +1,40 @@ +"""Tool contracts: definition (ToolContract) and result envelope (ToolResult).""" + +from __future__ import annotations + +from typing import Any + +from pydantic import Field + +from agent_core.contracts._base import ToolRiskTier, TraceableModel, VersionedModel +from agent_core.contracts.errors import ErrorEnvelope +from agent_core.contracts.models import CostUsage + + +class ToolContract(VersionedModel): + """Typed declaration of a tool (or MCP tool) the runtime may execute.""" + + name: str + version: str = "1" + risk_tier: ToolRiskTier = "read_only" + input_schema: dict[str, Any] = Field(default_factory=dict) + output_schema: dict[str, Any] = Field(default_factory=dict) + permissions: dict[str, Any] = Field(default_factory=dict) + timeout_seconds: float | None = None + rate_limit: dict[str, Any] = Field(default_factory=dict) + side_effects: str | None = None + audit_level: str = "standard" + owner: str | None = None + + +class ToolResult(TraceableModel): + """Standard envelope for the result of any tool/MCP invocation.""" + + tool: str + ok: bool = True + output: dict[str, Any] = Field(default_factory=dict) + error: ErrorEnvelope | None = None + latency_ms: float | None = None + cost: CostUsage | None = None + permission_decision: str | None = None + approval_decision: str | None = None diff --git a/agent_core/contracts/tracing.py b/agent_core/contracts/tracing.py new file mode 100644 index 0000000..8d59840 --- /dev/null +++ b/agent_core/contracts/tracing.py @@ -0,0 +1,43 @@ +"""TraceEvent (execution events) and AuditEvent (control-plane changes).""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any +from uuid import uuid4 + +from pydantic import Field + +from agent_core.contracts._base import TraceableModel, VersionedModel, utcnow +from agent_core.contracts.models import CostUsage + + +def _event_id() -> str: + return f"evt_{uuid4().hex}" + + +class TraceEvent(TraceableModel): + """A single event emitted during execution. + + Maps from engineering-loop ``loop_trace.json`` per-node summaries and NOC + ``traces``/``CaseEvent`` rows. + """ + + event_id: str = Field(default_factory=_event_id) + event_type: str + summary: str = "" + payload: dict[str, Any] = Field(default_factory=dict) + cost: CostUsage | None = None + timestamp: datetime = Field(default_factory=utcnow) + + +class AuditEvent(VersionedModel): + """Audit record for a control-plane change (graph edit, promotion, etc.).""" + + actor_id: str + action: str + target_type: str + target_id: str + before: dict[str, Any] | None = None + after: dict[str, Any] | None = None + created_at: datetime = Field(default_factory=utcnow) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fda8c27 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "agent-core" +version = "0.1.0" +description = "Shared typed contracts for the AS215932 Agent Runtime Framework (Phase 1 / §31 safe milestone)." +requires-python = ">=3.11" +dependencies = ["pydantic>=2,<3"] + +[project.optional-dependencies] +dev = ["pytest>=8", "pyyaml>=6", "ruff>=0.5", "mypy>=1.8", "types-PyYAML"] + +[tool.hatch.build.targets.wheel] +packages = ["agent_core"] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] + +[tool.mypy] +python_version = "3.11" +plugins = ["pydantic.mypy"] +ignore_missing_imports = true +warn_redundant_casts = true +disallow_untyped_defs = true + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/scripts/export_schemas.py b/scripts/export_schemas.py new file mode 100644 index 0000000..a659e58 --- /dev/null +++ b/scripts/export_schemas.py @@ -0,0 +1,40 @@ +"""Export JSON Schemas for every public contract to agent_core/contracts/schemas/. + +Run after changing contracts; CI checks the committed output is up to date. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import agent_core.contracts as contracts +from agent_core.contracts._base import TraceableModel, VersionedModel + +OUT_DIR = Path(__file__).resolve().parent.parent / "agent_core" / "contracts" / "schemas" + + +def model_classes() -> list[type[VersionedModel]]: + models: list[type[VersionedModel]] = [] + for name in contracts.__all__: + obj = getattr(contracts, name) + if ( + isinstance(obj, type) + and issubclass(obj, VersionedModel) + and obj not in (VersionedModel, TraceableModel) + ): + models.append(obj) + return sorted(models, key=lambda m: m.__name__) + + +def main() -> None: + OUT_DIR.mkdir(parents=True, exist_ok=True) + for model in model_classes(): + schema = model.model_json_schema() + path = OUT_DIR / f"{model.__name__}.schema.json" + path.write_text(json.dumps(schema, indent=2, sort_keys=True) + "\n") + print(f"exported {len(model_classes())} schemas to {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/tests/adapters/fixtures/approval_decision.sample.json b/tests/adapters/fixtures/approval_decision.sample.json new file mode 100644 index 0000000..0f88ddf --- /dev/null +++ b/tests/adapters/fixtures/approval_decision.sample.json @@ -0,0 +1,7 @@ +{ + "incident_id": "inc-2026-0628-bgp-07", + "decision": "approved", + "operator": "operator-1", + "comment": "Confirmed in lab; proceed with firewall reload.", + "decided_at": "2026-06-28T18:30:00+00:00" +} diff --git a/tests/adapters/fixtures/change_proposal.sample.json b/tests/adapters/fixtures/change_proposal.sample.json new file mode 100644 index 0000000..bab7b24 --- /dev/null +++ b/tests/adapters/fixtures/change_proposal.sample.json @@ -0,0 +1,16 @@ +{ + "proposal_id": "prop-abc123", + "incident_id": "inc-2026-0628-bgp-07", + "resource_id": "core-router-1", + "assessment": "Partial IPv6 forwarding loss to external destinations.", + "root_cause_hypothesis": "Firewall bogons6 table includes a multicast prefix, blocking outbound NDP on the WAN interface.", + "confidence": 0.78, + "evidence_refs": ["mcp:firewall_state:core-router-1", "mcp:ndp_state:core-router-1"], + "drift_findings": ["firewall table bogons6 differs from intended"], + "proposed_remediation": ["Remove the multicast prefix from bogons6", "Reload the firewall ruleset"], + "structured_actions": [ + {"tool": "os_service_restart", "args": {"service": "firewall"}, "risk": "prod_change"} + ], + "validation_status": "validated", + "human_review_rationale": "" +} diff --git a/tests/adapters/fixtures/context_pack.sample.json b/tests/adapters/fixtures/context_pack.sample.json new file mode 100644 index 0000000..75f06b0 --- /dev/null +++ b/tests/adapters/fixtures/context_pack.sample.json @@ -0,0 +1,23 @@ +{ + "id": "ctx_0123456789abcdef0123456789abcdef", + "task_id": "know_abc12345", + "role": "engineering_loop", + "generated_at": "2026-06-28T18:00:00+00:00", + "knowledge_snapshot": "2026-06-28-export-001", + "retrieval_version": "r3", + "policy_version": "p2", + "token_budget": 6000, + "max_chars": 24000, + "risk_level": "low", + "task": {"text": "vault-agent token templating cadence"}, + "sections": [ + {"name": "authoritative_source", "body": "vault_agent role manages env templating.", "refs": ["okf:vault-agent"]} + ], + "included_refs": [ + {"ref": "okf:vault-agent", "authority": "A1", "kind": "concept", "review_status": "reviewed"}, + {"ref": "network-operations:ansible/roles/vault_agent/tasks/main.yml", "authority": "A0", "kind": "source", "commit_sha": "deadbeef"} + ], + "excluded_refs": [], + "policy_decision": {"decision": "allow", "role": "engineering_loop"}, + "unresolved_questions": ["Is the restart handler idempotent?"] +} diff --git a/tests/adapters/fixtures/evidence_items.sample.json b/tests/adapters/fixtures/evidence_items.sample.json new file mode 100644 index 0000000..06b8f71 --- /dev/null +++ b/tests/adapters/fixtures/evidence_items.sample.json @@ -0,0 +1,14 @@ +[ + { + "tool": "firewall_state", + "summary": "bogons6 table contains a multicast prefix", + "direct_measurement": true, + "payload": {"host": "core-router-1", "table": "bogons6", "entries": 42} + }, + { + "tool": "ndp_state", + "summary": "no NDP neighbor for upstream gateway", + "direct_measurement": true, + "payload": {"host": "core-router-1", "iface": "wan0", "neighbors": 0} + } +] diff --git a/tests/adapters/fixtures/graph_state.sample.json b/tests/adapters/fixtures/graph_state.sample.json new file mode 100644 index 0000000..f77f1ac --- /dev/null +++ b/tests/adapters/fixtures/graph_state.sample.json @@ -0,0 +1,8 @@ +{ + "change_id": "chg-2026-0628-001", + "change_class": "infra_ansible", + "risk_level": "high", + "customer_impact": "possible", + "source_of_truth_files": ["ansible/roles/vault_agent/tasks/main.yml"], + "feature_request": "Tune vault-agent token templating cadence" +} diff --git a/tests/adapters/fixtures/loop_trace.sample.json b/tests/adapters/fixtures/loop_trace.sample.json new file mode 100644 index 0000000..872b527 --- /dev/null +++ b/tests/adapters/fixtures/loop_trace.sample.json @@ -0,0 +1,26 @@ +{ + "llm_outputs": [ + { + "role": "security_auditor", + "approved": false, + "proposed_mutation_paths": ["ansible/roles/vault_agent/tasks/main.yml"], + "source_files": ["ansible/roles/vault_agent/tasks/main.yml"], + "model_selection": {"provider": "anthropic", "model": "claude-sonnet-4-6", "tier": "strong"} + } + ], + "gate_results": [ + {"command": ["python", "-m", "compileall", "ansible"], "status": "passed", "returncode": 0}, + {"command": ["ansible-lint"], "status": "failed", "returncode": 2} + ], + "backend_results": [ + { + "repo": "network-operations", + "backend": "mock", + "status": "completed", + "iterations": 1, + "wall_clock_seconds": 12.4, + "cost": {"input_tokens": 1820, "output_tokens": 540, "usd": 0.0}, + "changed_paths": ["ansible/roles/vault_agent/tasks/main.yml"] + } + ] +} diff --git a/tests/adapters/fixtures/role_review.sample.json b/tests/adapters/fixtures/role_review.sample.json new file mode 100644 index 0000000..4d2ea35 --- /dev/null +++ b/tests/adapters/fixtures/role_review.sample.json @@ -0,0 +1,11 @@ +{ + "approved": false, + "validation_errors": [ + {"path": "ansible/roles/vault_agent/tasks/main.yml", "error": "missing restart handler notify"} + ], + "proposed_mutations": [ + {"path": "ansible/roles/vault_agent/tasks/main.yml", "content": "# ... updated ...", "operation": "replace"}, + {"path": "ansible/roles/vault_agent/handlers/main.yml", "content": "# ... new handler ...", "operation": "create"} + ], + "notes": "Needs a restart handler before approving." +} diff --git a/tests/adapters/test_engineering_adapter.py b/tests/adapters/test_engineering_adapter.py new file mode 100644 index 0000000..88512ac --- /dev/null +++ b/tests/adapters/test_engineering_adapter.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from agent_core.adapters import engineering_loop as eng + + +def test_task_envelope(load_fixture: Callable[[str], Any]) -> None: + state = load_fixture("graph_state.sample.json") + env = eng.task_envelope_from_graph_state(state) + assert env.task_id == "chg-2026-0628-001" + assert env.source == "engineering-loop" + assert env.risk_level == "high" + assert env.graph_id == "engineering-loop" + + +def test_decision(load_fixture: Callable[[str], Any]) -> None: + review = load_fixture("role_review.sample.json") + decision = eng.decision_from_role_review("security_auditor", review) + assert decision.approved is False + assert decision.decision == "reject" + assert len(decision.proposed_actions) == 2 + assert decision.agent_role == "security_auditor" + + +def test_trace_events(load_fixture: Callable[[str], Any]) -> None: + trace = load_fixture("loop_trace.sample.json") + events = eng.trace_events_from_loop_trace(trace, run_id="run-1") + types = [e.event_type for e in events] + assert "model_call" in types + assert "backend_execution" in types + backend = next(e for e in events if e.event_type == "backend_execution") + assert backend.cost is not None + assert backend.cost.input_tokens == 1820 + assert all(e.run_id == "run-1" for e in events) diff --git a/tests/adapters/test_knowledge_adapter.py b/tests/adapters/test_knowledge_adapter.py new file mode 100644 index 0000000..28f1b81 --- /dev/null +++ b/tests/adapters/test_knowledge_adapter.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from agent_core.adapters import knowledge as kn + + +def test_evidence_from_context_pack(load_fixture: Callable[[str], Any]) -> None: + pack = load_fixture("context_pack.sample.json") + evidence = kn.evidence_from_context_pack(pack) + assert len(evidence.sources) == 2 + assert evidence.authority_max == "A0" + assert evidence.metadata["context_pack_id"] == "ctx_0123456789abcdef0123456789abcdef" + + +def test_trace_event_from_context_pack(load_fixture: Callable[[str], Any]) -> None: + pack = load_fixture("context_pack.sample.json") + event = kn.trace_event_from_context_pack(pack, run_id="run-2") + assert event.event_type == "knowledge_context_pack" + assert event.run_id == "run-2" + + +def test_task_envelope_deterministic() -> None: + first = kn.task_envelope_from_context_request("same task") + second = kn.task_envelope_from_context_request("same task") + assert first.task_id == second.task_id + assert first.task_id.startswith("know_") + assert first.task_class == "knowledge_retrieval" diff --git a/tests/adapters/test_noc_adapter.py b/tests/adapters/test_noc_adapter.py new file mode 100644 index 0000000..8081ce8 --- /dev/null +++ b/tests/adapters/test_noc_adapter.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +from agent_core.adapters import noc_agent as noc + + +def test_decision(load_fixture: Callable[[str], Any]) -> None: + proposal = load_fixture("change_proposal.sample.json") + decision = noc.decision_from_change_proposal(proposal) + assert decision.confidence == 0.78 + assert len(decision.proposed_actions) == 3 + assert decision.evidence is not None + assert len(decision.evidence.sources) == 2 + + +def test_evidence(load_fixture: Callable[[str], Any]) -> None: + items = load_fixture("evidence_items.sample.json") + evidence = noc.evidence_from_items(items) + assert len(evidence.tool_results) == 2 + assert evidence.tool_results[0].tool == "firewall_state" + + +def test_approval(load_fixture: Callable[[str], Any]) -> None: + decision = load_fixture("approval_decision.sample.json") + approval = noc.approval_decision_from_noc(decision) + assert approval.decision == "approved" + assert approval.approver == "operator-1" + + +def test_cost_from_metrics() -> None: + cost = noc.cost_usage_from_model_metrics( + { + "active_model": "openrouter:deepseek/deepseek-v4-pro", + "provider": "openrouter", + "latency_ms": 2300.0, + "fallback_attempts": 1, + } + ) + assert cost.fallback_used is True + assert cost.latency_ms == 2300.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..40bba78 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import json +from collections.abc import Callable +from pathlib import Path +from typing import Any + +import pytest + +FIXTURES = Path(__file__).parent / "adapters" / "fixtures" + + +@pytest.fixture +def load_fixture() -> Callable[[str], Any]: + def _load(name: str) -> Any: + return json.loads((FIXTURES / name).read_text()) + + return _load diff --git a/tests/contracts/test_graphspecs_parse.py b/tests/contracts/test_graphspecs_parse.py new file mode 100644 index 0000000..e8f5ec2 --- /dev/null +++ b/tests/contracts/test_graphspecs_parse.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from pathlib import Path + +import yaml + +from agent_core.contracts.graph import GraphSpec + +GRAPHS = Path(__file__).resolve().parents[2] / "agent_core" / "contracts" / "graphs" + + +def _load(name: str) -> GraphSpec: + return GraphSpec.model_validate(yaml.safe_load((GRAPHS / name).read_text())) + + +def test_engineering_graph() -> None: + spec = _load("engineering-loop.draft.graph.yaml") + assert spec.graph_id == "engineering-loop" + assert len(spec.nodes) == 20 + kinds = {n.id: n.kind for n in spec.nodes} + assert kinds["human_signoff"] == "human_approval" + assert kinds["reflection"] == "finalizer" + + +def test_noc_graph() -> None: + spec = _load("noc-agent.draft.graph.yaml") + assert len(spec.nodes) == 13 + by_id = {n.id: n for n in spec.nodes} + assert by_id["approval_interrupt"].kind == "human_approval" + assert by_id["execute_approved_remediation"].risk_tier == "critical" + + +def test_knowledge_graph() -> None: + spec = _load("knowledge.draft.graph.yaml") + assert spec.entrypoint == "retrieve" + assert any(n.kind == "policy" for n in spec.nodes) diff --git a/tests/contracts/test_invalid_rejection.py b/tests/contracts/test_invalid_rejection.py new file mode 100644 index 0000000..6108dc5 --- /dev/null +++ b/tests/contracts/test_invalid_rejection.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from agent_core.contracts import HumanApprovalDecision, TaskEnvelope + + +def test_extra_field_forbidden() -> None: + with pytest.raises(ValidationError): + TaskEnvelope.model_validate( + {"task_id": "t", "task_class": "c", "source": "s", "bogus": 1} + ) + + +def test_missing_required() -> None: + with pytest.raises(ValidationError): + TaskEnvelope.model_validate({}) + + +def test_bad_literal_decision() -> None: + with pytest.raises(ValidationError): + HumanApprovalDecision.model_validate( + {"request_id": "i", "decision": "maybe", "approver": "op"} + ) + + +def test_bad_risk_level() -> None: + with pytest.raises(ValidationError): + TaskEnvelope.model_validate( + {"task_id": "t", "task_class": "c", "source": "s", "risk_level": "extreme"} + ) diff --git a/tests/contracts/test_json_roundtrip.py b/tests/contracts/test_json_roundtrip.py new file mode 100644 index 0000000..d28f463 --- /dev/null +++ b/tests/contracts/test_json_roundtrip.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from agent_core.contracts import ( + CostUsage, + DecisionPacket, + FeedbackEvent, + TaskEnvelope, + ToolResult, + TraceEvent, +) +from agent_core.contracts._base import VersionedModel + + +def _roundtrip(model: VersionedModel) -> None: + restored = type(model).model_validate_json(model.model_dump_json()) + assert restored == model + + +def test_roundtrips() -> None: + _roundtrip(TaskEnvelope(task_id="t", task_class="c", source="s", risk_level="high")) + _roundtrip(TraceEvent(event_type="x", cost=CostUsage(usd=0.5))) + _roundtrip(ToolResult(tool="t", output={"a": 1})) + _roundtrip(DecisionPacket(decision="approve", approved=True)) + _roundtrip(FeedbackEvent(feedback_id="f", signal_type="bad", categories=["hallucinated"])) diff --git a/tests/contracts/test_no_heavy_imports.py b/tests/contracts/test_no_heavy_imports.py new file mode 100644 index 0000000..c4f5fb1 --- /dev/null +++ b/tests/contracts/test_no_heavy_imports.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +import importlib +import sys + +HEAVY = ["langgraph", "pydantic_ai", "sqlalchemy", "langchain", "redis", "asyncpg"] + + +def test_contracts_import_is_dependency_light() -> None: + for mod in HEAVY: + sys.modules.pop(mod, None) + importlib.import_module("agent_core.contracts") + leaked = [m for m in HEAVY if m in sys.modules] + assert not leaked, f"contracts pulled in heavy deps: {leaked}" diff --git a/tests/contracts/test_schema_validation.py b/tests/contracts/test_schema_validation.py new file mode 100644 index 0000000..bf34b5d --- /dev/null +++ b/tests/contracts/test_schema_validation.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from agent_core.contracts import ( + CostUsage, + DecisionPacket, + ErrorEnvelope, + EvidencePacket, + FeedbackEvent, + HumanApprovalDecision, + PolicyGateResult, + RunContext, + TaskEnvelope, + ToolContract, + ToolResult, + TraceEvent, +) +from agent_core.contracts._base import SCHEMA_VERSION + + +def test_task_envelope_minimal() -> None: + t = TaskEnvelope(task_id="t1", task_class="noc_triage", source="noc-agent") + assert t.schema_version == SCHEMA_VERSION + assert t.risk_level == "low" + + +def test_trace_event_carries_cost() -> None: + e = TraceEvent(event_type="model_call", cost=CostUsage(usd=0.01, model="m")) + assert e.event_id.startswith("evt_") + assert e.cost is not None and e.cost.usd == 0.01 + + +def test_tool_contract_and_result() -> None: + c = ToolContract(name="net_ping", risk_tier="read_only") + r = ToolResult(tool="net_ping", ok=True, output={"rtt_ms": 1.2}) + assert c.risk_tier == "read_only" + assert r.ok + + +def test_decision_and_evidence() -> None: + d = DecisionPacket(decision="approve", approved=True, evidence=EvidencePacket()) + assert d.approved is True + assert d.evidence is not None + + +def test_other_core_models() -> None: + assert RunContext(run_id="r1").run_id == "r1" + assert ErrorEnvelope(error_type="X", message="boom").retryable is False + assert PolicyGateResult(allow=False).allow is False + assert FeedbackEvent(feedback_id="f1").actor_role == "operator" + approval = HumanApprovalDecision(request_id="i1", decision="approved", approver="op") + assert approval.decision == "approved" diff --git a/tests/schemas/test_schema_export.py b/tests/schemas/test_schema_export.py new file mode 100644 index 0000000..e1ffe27 --- /dev/null +++ b/tests/schemas/test_schema_export.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import agent_core.contracts as contracts +from agent_core.contracts._base import TraceableModel, VersionedModel + +SCHEMA_DIR = Path(__file__).resolve().parents[2] / "agent_core" / "contracts" / "schemas" + + +def _models() -> list[type[VersionedModel]]: + out: list[type[VersionedModel]] = [] + for name in contracts.__all__: + obj = getattr(contracts, name) + if ( + isinstance(obj, type) + and issubclass(obj, VersionedModel) + and obj not in (VersionedModel, TraceableModel) + ): + out.append(obj) + return out + + +def test_committed_schemas_exist_and_match() -> None: + assert SCHEMA_DIR.exists(), "run scripts/export_schemas.py" + for model in _models(): + path = SCHEMA_DIR / f"{model.__name__}.schema.json" + assert path.exists(), f"missing schema for {model.__name__}; run scripts/export_schemas.py" + committed = json.loads(path.read_text()) + assert committed == model.model_json_schema(), f"stale schema for {model.__name__}"