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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/coreason_runtime/api/discovery_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (c) 2026 CoReason, Inc.
#
# This software is proprietary and dual-licensed
# Licensed under the Prosperity Public License 3.0 (the "License")
# A copy of the license is available at <https://prosperitylicense.com/versions/3.0.0>
# For details, see the LICENSE file
# Commercial use beyond a 30-day trial requires a separate license
#
# Source Code: <https://github.com/CoReason-AI/coreason-runtime>

from typing import Any

from fastapi import APIRouter
from pydantic import BaseModel, Field

from coreason_runtime.execution_plane.discovery_indexer import DiscoveryIndexer

discovery_router = APIRouter(prefix="/api/v1/discovery", tags=["Semantic Discovery"])


class DiscoverySearchRequest(BaseModel):
query: str = Field(..., description="The semantic query to search for capabilities.")
limit: int = Field(5, description="Maximum number of results to return.")
tenant_cid: str = Field(
"889955217295c2bfef2d6812071b633b0819477e67f57853febf116f69f30531",
description="Tenant CID for segregation.",
)


@discovery_router.post("/search")
async def search_capabilities(request: DiscoverySearchRequest) -> list[dict[str, Any]]:
"""AGENT INSTRUCTION: Perform semantic discovery of URN-addressable capabilities.

CAUSAL AFFORDANCE: Resolves a natural language intent into a ranked list of available tools.

EPISTEMIC BOUNDS: Limited to the capabilities indexed in the tenant's vector store.

MCP ROUTING TRIGGERS: Semantic Search, Tool Discovery, Vector Retrieval, Intent Matching
"""
try:
indexer = DiscoveryIndexer(tenant_cid=request.tenant_cid)
# Ensure local WASM tools are indexed
indexer.sync_local_wasm()
# Note: Remote MCP sync is typically handled by the NemoClaw bridge out-of-process,
# but search_capabilities will find whatever is already in the LanceDB table.
return indexer.search_capabilities(query=request.query, limit=request.limit, tenant_cid=request.tenant_cid)
except Exception as e:
from fastapi import HTTPException

raise HTTPException(status_code=500, detail=f"Discovery search failed: {e}") from e
2 changes: 2 additions & 0 deletions src/coreason_runtime/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def create_app() -> Any:

FastAPIInstrumentor.instrument_app(api_app)

from coreason_runtime.api.discovery_router import discovery_router
from coreason_runtime.api.oracle import router as oracle_router
from coreason_runtime.api.predict_router import predict_router
from coreason_runtime.api.schema import router as schema_router
Expand All @@ -78,6 +79,7 @@ def create_app() -> Any:
api_app.include_router(oracle_router)
api_app.include_router(predict_router)
api_app.include_router(shocks_router)
api_app.include_router(discovery_router)
return api_app


Expand Down
62 changes: 62 additions & 0 deletions tests/api/test_discovery_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright (c) 2026 CoReason, Inc.
#
# This software is proprietary and dual-licensed
# Licensed under the Prosperity Public License 3.0 (the "License")
# A copy of the license is available at <https://prosperitylicense.com/versions/3.0.0>
# For details, see the LICENSE file
# Commercial use beyond a 30-day trial requires a separate license
#
# Source Code: <https://github.com/CoReason-AI/coreason-runtime>

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from unittest.mock import AsyncMock, patch, MagicMock

from coreason_runtime.api.discovery_router import discovery_router

app = FastAPI()
app.include_router(discovery_router)

@pytest.fixture
async def client():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as c:
yield c

@pytest.mark.asyncio
async def test_search_discovery_success(client: AsyncClient):
mock_results = [
{
"urn": "urn:coreason:actionspace:solver:test:v1",
"score": 0.95
}
]

with patch("coreason_runtime.api.discovery_router.DiscoveryIndexer") as MockIndexer:
# Configure the mock instance
mock_instance = MockIndexer.return_value
mock_instance.search_capabilities = MagicMock(return_value=mock_results)

payload = {"query": "find test tools", "limit": 5}
response = await client.post("/api/v1/discovery/search", json=payload)

assert response.status_code == 200

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert response.json() == mock_results

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
mock_instance.search_capabilities.assert_called_once_with(
query="find test tools",
limit=5,
tenant_cid="889955217295c2bfef2d6812071b633b0819477e67f57853febf116f69f30531"
)

@pytest.mark.asyncio
async def test_search_discovery_error(client: AsyncClient):
with patch("coreason_runtime.api.discovery_router.DiscoveryIndexer") as MockIndexer:
mock_instance = MockIndexer.return_value
mock_instance.search_capabilities = MagicMock(side_effect=Exception("Database error"))

payload = {"query": "fail query"}
response = await client.post("/api/v1/discovery/search", json=payload)

assert response.status_code == 500

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert "Discovery search failed" in response.json()["detail"]

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Loading