diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 740e9b45..ae76b903 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -41,6 +41,7 @@ jobs: - { image: evidence-console, context: apps/evidence-console, dockerfile: Dockerfile } - { image: evidence-receipts, context: apps/evidence-receipts, dockerfile: Dockerfile } - { image: eval-fabric-api, context: apps/eval-fabric-api, dockerfile: Dockerfile } + - { image: agentic-os-api, context: apps/agentic-os-api, dockerfile: Dockerfile } - { image: hellgraph-service, context: apps/hellgraph-service, dockerfile: Dockerfile } - { image: osm-map-api, context: ., dockerfile: apps/osm-map-api/Dockerfile } - { image: workspace-mail, context: services/workspace-mail, dockerfile: Dockerfile } diff --git a/apps/agentic-os-api/Dockerfile b/apps/agentic-os-api/Dockerfile new file mode 100644 index 00000000..2f6a3e4d --- /dev/null +++ b/apps/agentic-os-api/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.12-slim +WORKDIR /app +COPY requirements.txt ./requirements.txt +RUN pip install --no-cache-dir -r requirements.txt +COPY app ./app +EXPOSE 8080 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/apps/agentic-os-api/README.md b/apps/agentic-os-api/README.md new file mode 100644 index 00000000..a4d9a7fa --- /dev/null +++ b/apps/agentic-os-api/README.md @@ -0,0 +1,25 @@ +# agentic-os-api + +The coordination service for the **agentic operating system** — agent pods +pursuing objectives across the estate under a governed capture cadence. + +Serves the canonical agentic-OS objects (`Opportunity` / `AgentPod` / +`ReadinessScore` / `CaptureCadence`) that the cockpit console renders and the +pods coordinate against. Object shapes conform to the **sourceos-spec** +agentic-OS contract and compose over **prophet-workspace** (ProfessionalWorkroom +/ OrgGovControlRoom) and **prophet-mesh** (agent-choir + estate graph). + +Read-only seed today; a live registry adapter will resolve the same URNs from the +workspace + estate graph. + +## Endpoints +- `GET /health` +- `GET /opportunities` · `GET /opportunities/{slug}` (with readiness + cadence) +- `GET /pods` +- `GET /cadence` + +## Run +``` +pip install -r requirements.txt +uvicorn app.main:app --port 8080 +``` diff --git a/apps/agentic-os-api/app/__init__.py b/apps/agentic-os-api/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/agentic-os-api/app/data.py b/apps/agentic-os-api/app/data.py new file mode 100644 index 00000000..eb5397cb --- /dev/null +++ b/apps/agentic-os-api/app/data.py @@ -0,0 +1,86 @@ +"""Canonical agentic-OS objects served by the coordination service. + +Shapes conform to the sourceos-spec agentic-OS contract (Opportunity / AgentPod / +ReadinessScore / CaptureCadence), which composes over prophet-workspace +(ProfessionalWorkroom / OrgGovControlRoom) and prophet-mesh (agent-choir + estate +graph). This is the seed dataset the service serves until a live registry adapter +resolves the same URNs from the workspace + estate graph. +""" +from __future__ import annotations + +READINESS_DIMS = [ + "buyerProblem", "solutionHypothesis", "sharedLibraries", "agentPod", "partnerArchetype", + "namedPartnerTargets", "oemLane", "artifactPack", "deltaControl", "questions", "pricing", "pastPerformance", +] + +PODS = [ + {"id": "urn:srcos:agent-pod:capture-lead", "type": "AgentPod", "specVersion": "2.0.0", + "role": "Capture Lead", "mandate": "Own pursuit strategy, buyer map, milestones, and decisions.", + "inputs": ["signals", "updates", "Q&A", "competitive intel"], + "outputs": ["pursuit plan", "gate decisions", "action backlog"], + "repoAnchors": ["sociosphere", "socioprophet"], "status": "active", "choirRole": "governance-sentinel"}, + {"id": "urn:srcos:agent-pod:technical-solution", "type": "AgentPod", "specVersion": "2.0.0", + "role": "Technical Solution", "mandate": "Design service model, architecture, transition, operating model.", + "inputs": ["scope", "priorities", "reusable patterns"], "outputs": ["solution narrative", "architecture"], + "repoAnchors": ["prophet-platform", "prophet-platform-standards"], "status": "active", "choirRole": "planning-agent"}, + {"id": "urn:srcos:agent-pod:evidence-qa", "type": "AgentPod", "specVersion": "2.0.0", + "role": "Evidence / QA", "mandate": "Run review gates and coherence checks across all objectives.", + "inputs": ["draft artifacts", "matrices"], "outputs": ["gate report", "defect list"], + "repoAnchors": ["agentplane", "policy-fabric"], "status": "active", "choirRole": "governance-sentinel"}, +] + +CADENCE = { + "id": "urn:srcos:capture-cadence:standard-8wk", "type": "CaptureCadence", "specVersion": "2.0.0", + "name": "Standard 8-week capture sprint", "currentWeek": 4, + "weeks": [ + {"week": 0, "objective": "Intake + normalize", "minReadiness": 0.2, "exitDecision": "pursue / watch / pause"}, + {"week": 4, "objective": "Artifact pack v1", "minReadiness": 0.5, "exitDecision": "Gate 1 complete"}, + {"week": 8, "objective": "Delta sprint", "minReadiness": 0.8, "exitDecision": "go-forward after delta"}, + ], +} + + +def _readiness(opp_slug: str, scores: dict[str, int]) -> dict: + full = {d: scores.get(d, 0) for d in READINESS_DIMS} + total = sum(full.values()) + pct = round(total / (len(READINESS_DIMS) * 3) * 100) + rag = "Green" if pct >= 70 else "Amber" if pct >= 40 else "Red" + return { + "id": f"urn:srcos:readiness-score:{opp_slug}", "type": "ReadinessScore", "specVersion": "2.0.0", + "opportunityRef": f"urn:srcos:opportunity:{opp_slug}", "dimensions": full, + "total": total, "max": 36, "readinessPct": pct, "rag": rag, "nextGate": "Gate 1", + "policyRef": "policy://capture/burden-of-proof/gate-1", + } + + +OPPORTUNITIES = [ + {"id": "urn:srcos:opportunity:health-devsecops", "type": "Opportunity", "specVersion": "2.0.0", + "name": "Health Services DevSecOps", "cluster": "Health", "missionOwner": "PDS", + "buyingProblem": "Sustain legacy health apps while modernizing safely outside the Oracle Health baseline.", + "deliveryPattern": "Continuity-plus-modernization delivery cell; governed DevSecOps.", + "reuseRepos": ["prophet-platform", "agentplane", "policy-fabric"], + "podRefs": ["urn:srcos:agent-pod:capture-lead", "urn:srcos:agent-pod:technical-solution", "urn:srcos:agent-pod:evidence-qa"], + "workroomRef": "workroom://health-devsecops", "controlRoomRef": "controlroom://health-devsecops", + "telos": {"objective": "Intelligence serves human flourishing", "constraints": ["non-domination", "consent", "dignity"]}, + "readinessRef": "urn:srcos:readiness-score:health-devsecops", "status": "Active", + "winTheme": "We preserve continuity while industrializing governed modernization."}, + {"id": "urn:srcos:opportunity:zta-acceleration", "type": "Opportunity", "specVersion": "2.0.0", + "name": "ZTA Acceleration", "cluster": "Cyber", "missionOwner": "OIS", + "buyingProblem": "Accelerate zero-trust across identity, network, and data without breaking operations.", + "deliveryPattern": "Identity-control-plane cell with runtime identity semantics and evidence refs.", + "reuseRepos": ["mcp-a2a-zero-trust", "policy-fabric"], + "podRefs": ["urn:srcos:agent-pod:technical-solution", "urn:srcos:agent-pod:evidence-qa"], + "workroomRef": "workroom://zta-acceleration", "controlRoomRef": "controlroom://zta-acceleration", + "telos": {"objective": "Intelligence serves human flourishing", "constraints": ["non-domination", "consent", "dignity"]}, + "readinessRef": "urn:srcos:readiness-score:zta-acceleration", "status": "Active", + "winTheme": "Zero trust as a governed control plane with evidence at every grant."}, +] + +READINESS = { + "health-devsecops": _readiness("health-devsecops", {"buyerProblem": 3, "solutionHypothesis": 2, "sharedLibraries": 3, "agentPod": 2, "partnerArchetype": 2, "oemLane": 1, "artifactPack": 2, "deltaControl": 2, "questions": 1}), + "zta-acceleration": _readiness("zta-acceleration", {"buyerProblem": 3, "solutionHypothesis": 2, "sharedLibraries": 3, "agentPod": 2, "oemLane": 1, "artifactPack": 2, "deltaControl": 2}), +} + + +def opp_slug(opp: dict) -> str: + return opp["id"].rsplit(":", 1)[-1] diff --git a/apps/agentic-os-api/app/main.py b/apps/agentic-os-api/app/main.py new file mode 100644 index 00000000..cbe22021 --- /dev/null +++ b/apps/agentic-os-api/app/main.py @@ -0,0 +1,44 @@ +"""Agentic OS API — the coordination service for the agentic operating system. + +Serves the canonical agentic-OS objects (Opportunity / AgentPod / ReadinessScore +/ CaptureCadence) the cockpit console renders and the pods coordinate against. +Objects conform to the sourceos-spec agentic-OS contract and compose over +prophet-workspace (workrooms) + prophet-mesh (choir + estate graph). Read-only +seed for now; a live registry adapter resolves the same URNs from the workspace +and estate graph. +""" +from __future__ import annotations + +from fastapi import FastAPI, HTTPException + +from .data import CADENCE, OPPORTUNITIES, PODS, READINESS, opp_slug + +app = FastAPI(title="Prophet Platform Agentic OS API", version="0.1.0") + + +@app.get("/health") +def health() -> dict: + return {"status": "ok", "service": "agentic-os-api", "objectives": len(OPPORTUNITIES), "pods": len(PODS)} + + +@app.get("/opportunities") +def list_opportunities() -> dict: + return {"opportunities": OPPORTUNITIES, "count": len(OPPORTUNITIES)} + + +@app.get("/opportunities/{slug}") +def get_opportunity(slug: str) -> dict: + opp = next((o for o in OPPORTUNITIES if opp_slug(o) == slug), None) + if opp is None: + raise HTTPException(status_code=404, detail=f"opportunity {slug!r} not found") + return {"opportunity": opp, "readiness": READINESS.get(slug), "cadence": CADENCE} + + +@app.get("/pods") +def list_pods() -> dict: + return {"pods": PODS, "count": len(PODS)} + + +@app.get("/cadence") +def get_cadence() -> dict: + return CADENCE diff --git a/apps/agentic-os-api/pytest.ini b/apps/agentic-os-api/pytest.ini new file mode 100644 index 00000000..5ee64771 --- /dev/null +++ b/apps/agentic-os-api/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = tests diff --git a/apps/agentic-os-api/requirements-test.txt b/apps/agentic-os-api/requirements-test.txt new file mode 100644 index 00000000..719f860e --- /dev/null +++ b/apps/agentic-os-api/requirements-test.txt @@ -0,0 +1,2 @@ +pytest>=8.0 +httpx>=0.27 diff --git a/apps/agentic-os-api/requirements.txt b/apps/agentic-os-api/requirements.txt new file mode 100644 index 00000000..531efda7 --- /dev/null +++ b/apps/agentic-os-api/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.115.0 +uvicorn==0.30.6 diff --git a/apps/agentic-os-api/tests/test_api.py b/apps/agentic-os-api/tests/test_api.py new file mode 100644 index 00000000..8d2f205f --- /dev/null +++ b/apps/agentic-os-api/tests/test_api.py @@ -0,0 +1,47 @@ +"""Tests for the Agentic OS coordination API.""" +from fastapi.testclient import TestClient + +from app.data import READINESS_DIMS +from app.main import app + +client = TestClient(app) + + +def test_health(): + r = client.get("/health") + assert r.status_code == 200 + assert r.json()["status"] == "ok" + + +def test_opportunities_conform_to_contract(): + r = client.get("/opportunities") + assert r.status_code == 200 + opps = r.json()["opportunities"] + assert len(opps) >= 2 + for o in opps: + assert o["type"] == "Opportunity" + assert o["id"].startswith("urn:srcos:opportunity:") + # Composes over workspace + carries the Telos. + assert o["workroomRef"].startswith("workroom://") + assert set(o["telos"]) == {"objective", "constraints"} + + +def test_opportunity_detail_has_readiness_and_cadence(): + r = client.get("/opportunities/health-devsecops") + assert r.status_code == 200 + body = r.json() + rd = body["readiness"] + assert rd["type"] == "ReadinessScore" + assert set(rd["dimensions"]) == set(READINESS_DIMS) + assert rd["total"] == 18 and rd["readinessPct"] == 50 and rd["rag"] == "Amber" + assert body["cadence"]["currentWeek"] == 4 + + +def test_pods_align_to_choir(): + pods = client.get("/pods").json()["pods"] + assert all(p["id"].startswith("urn:srcos:agent-pod:") for p in pods) + assert any(p["choirRole"] == "governance-sentinel" for p in pods) + + +def test_missing_opportunity_404(): + assert client.get("/opportunities/nope").status_code == 404 diff --git a/deploy/argocd/platform-services.yaml b/deploy/argocd/platform-services.yaml index 3c3beb76..c1e6967f 100644 --- a/deploy/argocd/platform-services.yaml +++ b/deploy/argocd/platform-services.yaml @@ -19,6 +19,7 @@ spec: - { name: evidence-console } - { name: evidence-receipts } - { name: eval-fabric-api } + - { name: agentic-os-api } - { name: hellgraph-service } - { name: osm-map-api } - { name: reasoning-failure-runner } diff --git a/deploy/values/agentic-os-api.yaml b/deploy/values/agentic-os-api.yaml new file mode 100644 index 00000000..f80621bd --- /dev/null +++ b/deploy/values/agentic-os-api.yaml @@ -0,0 +1,3 @@ +# agentic-os-api +image: { repository: agentic-os-api, tag: latest } +service: { port: 8080, portName: http }