kontextual-engine/src/kontextual_engine/api/app.py

1921 lines
76 KiB
Python
Raw Normal View History

2026-05-06 19:30:49 +02:00
"""Versioned FastAPI service skeleton.
The service layer is intentionally thin: route handlers translate HTTP
requests into service/runtime contracts and must not own domain behavior.
"""
from __future__ import annotations
import json
2026-05-06 19:30:49 +02:00
from dataclasses import dataclass, field
from importlib import metadata
from typing import Any
from kontextual_engine.adapters.memory import InMemoryAssetRegistryRepository
from kontextual_engine.core import (
Actor,
ActorType,
AuditEvent,
AuditOutcome,
Classification,
ContextEntity,
ContextEntityType,
IngestionIdentityPolicy,
IngestionJobStatus,
LifecycleState,
MetadataRecord,
OperationContext,
PolicyDecision,
PolicyEffect,
RelationshipTargetKind,
RetrievalFeedbackLabel,
SourceReference,
TransformationRunStatus,
WorkflowExceptionKind,
WorkflowExceptionStatus,
WorkflowInputDefinition,
WorkflowReviewDecisionType,
WorkflowReviewStatus,
WorkflowRunStatus,
WorkflowStepDefinition,
WorkflowTemplate,
new_id,
utc_now,
)
from kontextual_engine.errors import AuthorizationError, KontextualError, NotFoundError, ValidationError
from kontextual_engine.ports import AllowAllPolicyGateway, AssetRegistryRepository, PolicyGateway
from kontextual_engine.services import (
AssetIngestionService,
AssetQueryRequest,
AssetRegistryService,
AssetRetrievalService,
ContextEntityQueryRequest,
RelationshipQueryRequest,
RetrievalFeedbackRequest,
TransformationRequest,
TransformationService,
WorkflowInvocation,
WorkflowService,
)
2026-05-06 19:30:49 +02:00
API_VERSION = "v1"
OPENAPI_VERSION = "1.0.0"
AGENT_OPERATION_CATALOG: tuple[dict[str, Any], ...] = (
{
"operation_id": "inspect_asset",
"description": "Read one asset envelope by ID.",
"input_schema": {"required": ["asset_id"]},
"output_schema": {"type": "asset"},
"required_permissions": ["agent.operation.inspect_asset", "asset.retrieve"],
"audit_operation": "agent.operation.inspect_asset",
"failure_modes": ["not_found", "permission_denied"],
"dry_run_supported": True,
},
{
"operation_id": "retrieve_asset",
"description": "Read one source-grounded asset bundle with metadata, representations, and relationships.",
"input_schema": {"required": ["asset_id"]},
"output_schema": {"type": "asset_bundle"},
"required_permissions": ["agent.operation.retrieve_asset", "asset.retrieve"],
"audit_operation": "agent.operation.retrieve_asset",
"failure_modes": ["not_found", "permission_denied"],
"dry_run_supported": True,
},
{
"operation_id": "search_assets",
"description": "Run a governed retrieval query over assets.",
"input_schema": {"required": ["query"]},
"output_schema": {"type": "asset_query_result"},
"required_permissions": ["agent.operation.search_assets", "retrieval.assets.query"],
"audit_operation": "agent.operation.search_assets",
"failure_modes": ["permission_denied", "validation_error", "zero_results"],
"dry_run_supported": True,
},
{
"operation_id": "assemble_context",
"description": "Assemble a non-durable source-grounded context preview from a bounded asset query.",
"input_schema": {"required": ["query"], "optional": ["intent", "instructions", "constraints"]},
"output_schema": {"type": "context_preview"},
"required_permissions": ["agent.operation.assemble_context", "retrieval.assets.query"],
"audit_operation": "agent.operation.assemble_context",
"failure_modes": ["permission_denied", "validation_error", "zero_results"],
"dry_run_supported": True,
},
{
"operation_id": "enrich_metadata",
"description": "Add one metadata record to an asset.",
"input_schema": {"required": ["asset_id", "metadata"]},
"output_schema": {"type": "asset_change"},
"required_permissions": ["agent.operation.enrich_metadata", "asset.metadata.add"],
"audit_operation": "agent.operation.enrich_metadata",
"failure_modes": ["not_found", "permission_denied", "validation_error", "version_conflict"],
"dry_run_supported": True,
},
{
"operation_id": "classify_asset",
"description": "Request the classify transformation operation for an asset.",
"input_schema": {"required": ["asset_id"]},
"output_schema": {"type": "transformation_run_result"},
"required_permissions": ["agent.operation.classify_asset", "transformation.run.execute"],
"audit_operation": "agent.operation.classify_asset",
"failure_modes": ["permission_denied", "adapter_unavailable", "operation_failed"],
"dry_run_supported": True,
},
{
"operation_id": "transform_asset",
"description": "Execute a registered transformation operation.",
"input_schema": {"required": ["transformation"]},
"output_schema": {"type": "transformation_run_result"},
"required_permissions": ["agent.operation.transform_asset", "transformation.run.execute"],
"audit_operation": "agent.operation.transform_asset",
"failure_modes": ["permission_denied", "adapter_unavailable", "operation_failed"],
"dry_run_supported": True,
},
{
"operation_id": "invoke_workflow",
"description": "Invoke a registered workflow template.",
"input_schema": {"required": ["workflow"]},
"output_schema": {"type": "workflow_run_result"},
"required_permissions": ["agent.operation.invoke_workflow", "workflow.run.execute"],
"audit_operation": "agent.operation.invoke_workflow",
"failure_modes": ["not_found", "permission_denied", "operation_failed", "review_required"],
"dry_run_supported": True,
},
{
"operation_id": "submit_review",
"description": "Submit a decision for one open workflow review task.",
"input_schema": {"required": ["run_id", "review_id", "decision"]},
"output_schema": {"type": "workflow_run_result"},
"required_permissions": ["agent.operation.submit_review", "workflow.review.decide"],
"audit_operation": "agent.operation.submit_review",
"failure_modes": ["not_found", "permission_denied", "review_not_open", "operation_failed"],
"dry_run_supported": True,
},
{
"operation_id": "report_result",
"description": "Record an agent result report without mutating domain assets.",
"input_schema": {"required": ["summary"], "optional": ["result_ref", "metadata"]},
"output_schema": {"type": "agent_report"},
"required_permissions": ["agent.operation.report_result"],
"audit_operation": "agent.operation.report_result",
"failure_modes": ["permission_denied", "validation_error"],
"dry_run_supported": True,
},
)
2026-05-06 19:30:49 +02:00
@dataclass
class ServiceRuntime:
repository: AssetRegistryRepository = field(default_factory=InMemoryAssetRegistryRepository)
policy_gateway: PolicyGateway = field(default_factory=AllowAllPolicyGateway)
2026-05-06 19:30:49 +02:00
api_version: str = API_VERSION
service_name: str = "kontextual-engine"
started_at: str = field(default_factory=lambda: utc_now().isoformat())
def asset_service(self) -> AssetRegistryService:
return AssetRegistryService(self.repository, policy_gateway=self.policy_gateway)
def ingestion_service(self) -> AssetIngestionService:
return AssetIngestionService(self.repository, asset_service=self.asset_service())
def retrieval_service(self) -> AssetRetrievalService:
return AssetRetrievalService(self.repository, policy_gateway=self.policy_gateway)
def transformation_service(self) -> TransformationService:
return TransformationService(
self.repository,
policy_gateway=self.policy_gateway,
asset_service=self.asset_service(),
)
def workflow_service(self) -> WorkflowService:
return WorkflowService(
self.repository,
transformation_service=self.transformation_service(),
policy_gateway=self.policy_gateway,
)
def operation_context(
self,
*,
actor_id: str = "api-user",
actor_type: str = "human",
display_name: str | None = None,
external_ref: str | None = None,
correlation_id: str | None = None,
groups: list[str] | None = None,
delegated_actor_id: str | None = None,
delegated_actor_type: str = "human",
delegated_actor_display_name: str | None = None,
delegated_actor_external_ref: str | None = None,
delegated_actor_groups: list[str] | None = None,
request_scope: dict[str, Any] | None = None,
policy_scope: dict[str, Any] | None = None,
agent_id: str | None = None,
agent_name: str | None = None,
agent_run_id: str | None = None,
agent_tool: str | None = None,
metadata: dict[str, Any] | None = None,
) -> OperationContext:
actor_metadata = dict(metadata or {})
agent_metadata = _agent_metadata(
agent_id=agent_id,
agent_name=agent_name,
agent_run_id=agent_run_id,
agent_tool=agent_tool,
)
if agent_metadata:
actor_metadata["agent"] = agent_metadata
actor = Actor.create(
ActorType(actor_type),
actor_id=actor_id,
display_name=display_name,
external_ref=external_ref,
groups=groups,
metadata=actor_metadata,
)
delegated_actor = None
if delegated_actor_id:
delegated_actor = Actor.create(
ActorType(delegated_actor_type),
actor_id=delegated_actor_id,
display_name=delegated_actor_display_name,
external_ref=delegated_actor_external_ref,
groups=delegated_actor_groups,
)
context_metadata: dict[str, Any] = {}
if agent_metadata:
context_metadata["agent"] = agent_metadata
if delegated_actor is not None:
context_metadata["delegation"] = {
"mode": "on_behalf_of",
"actor_id": actor.id,
"delegated_actor_id": delegated_actor.id,
}
return OperationContext.create(
actor,
correlation_id=correlation_id,
delegated_actor=delegated_actor,
request_scope=request_scope,
policy_scope=policy_scope,
metadata=context_metadata,
)
2026-05-06 19:30:49 +02:00
@property
def package_version(self) -> str:
try:
return metadata.version("kontextual-engine")
except metadata.PackageNotFoundError:
return "0.1.0"
def health(self) -> dict[str, Any]:
return {
"status": "ok",
"service": self.service_name,
"api_version": self.api_version,
"package_version": self.package_version,
"started_at": self.started_at,
}
def readiness(self) -> dict[str, Any]:
checks: dict[str, dict[str, Any]] = {}
try:
asset_count = len(self.repository.list_assets())
checks["asset_registry"] = {
"status": "ok",
"repository": type(self.repository).__name__,
"asset_count": asset_count,
}
except Exception as exc:
checks["asset_registry"] = {
"status": "error",
"repository": type(self.repository).__name__,
"error_type": type(exc).__name__,
"message": str(exc),
}
ready = all(item["status"] == "ok" for item in checks.values())
return {
"status": "ready" if ready else "not_ready",
"ready": ready,
"service": self.service_name,
"api_version": self.api_version,
"checks": checks,
}
def version(self) -> dict[str, Any]:
return {
"service": self.service_name,
"api_version": self.api_version,
"package_version": self.package_version,
"openapi_version": OPENAPI_VERSION,
}
def create_asset(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
classification = Classification.from_dict(payload["classification"])
result = self.asset_service().create_asset(
payload["title"],
classification,
context,
asset_id=payload.get("asset_id"),
source_refs=[_source_reference(item) for item in payload.get("source_refs", ())],
metadata_records=[_metadata_record(item) for item in payload.get("metadata_records", ())],
idempotency_key=payload.get("idempotency_key"),
)
return _asset_change_result(result)
def get_asset(self, asset_id: str) -> dict[str, Any]:
return self.asset_service().get_asset(asset_id).to_dict()
def list_assets(
self,
*,
lifecycle: str | None = None,
asset_type: str | None = None,
sensitivity: str | None = None,
owner: str | None = None,
topic: str | None = None,
review_state: str | None = None,
) -> dict[str, Any]:
assets = self.asset_service().list_assets(
lifecycle=LifecycleState(lifecycle) if lifecycle else None,
asset_type=asset_type,
sensitivity=sensitivity,
owner=owner,
topic=topic,
review_state=review_state,
)
return {"items": [asset.to_dict() for asset in assets], "count": len(assets)}
def add_metadata_record(
self,
asset_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
result = self.asset_service().add_metadata_record(
asset_id,
_metadata_record(payload),
context,
expected_current_version_id=payload.get("expected_current_version_id"),
)
return _asset_change_result(result)
def list_metadata_records(self, asset_id: str) -> dict[str, Any]:
records = self.repository.list_metadata_records(asset_id)
return {"items": [record.to_dict() for record in records], "count": len(records)}
def transition_lifecycle(
self,
asset_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
result = self.asset_service().transition_lifecycle(
asset_id,
LifecycleState(payload["lifecycle"]),
context,
expected_current_version_id=payload.get("expected_current_version_id"),
)
return _asset_change_result(result)
def create_relationship(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
target_kind = RelationshipTargetKind(payload.get("target_kind", RelationshipTargetKind.ASSET.value))
service = self.asset_service()
if target_kind == RelationshipTargetKind.CONTEXT_ENTITY:
entity_payload = payload.get("context_entity") or {}
entity = ContextEntity(
entity_id=payload["target_id"],
entity_type=ContextEntityType(entity_payload.get("entity_type", ContextEntityType.TOPIC.value)),
name=entity_payload.get("name", payload["target_id"]),
external_ref=entity_payload.get("external_ref"),
metadata=dict(entity_payload.get("metadata", {})),
)
result = service.link_asset_to_context_entity(
payload["source_asset_id"],
entity,
payload["predicate"],
context,
confidence=payload.get("confidence"),
provenance=dict(payload.get("provenance", {})),
expected_current_version_id=payload.get("expected_current_version_id"),
)
else:
result = service.link_asset_to_asset(
payload["source_asset_id"],
payload["target_id"],
payload["predicate"],
context,
confidence=payload.get("confidence"),
provenance=dict(payload.get("provenance", {})),
expected_current_version_id=payload.get("expected_current_version_id"),
)
return {
"relationship": result.relationship.to_dict(),
"version": result.version.to_dict(),
"audit_event": result.audit_event.to_dict(),
"policy_decision": result.policy_decision.to_dict(),
}
def list_relationships(
self,
*,
source_id: str | None = None,
target_id: str | None = None,
) -> dict[str, Any]:
relationships = self.repository.list_relationships(source_id=source_id, target_id=target_id)
return {
"items": [relationship.to_dict() for relationship in relationships],
"count": len(relationships),
}
def list_audit_events(
self,
*,
target: str | None = None,
correlation_id: str | None = None,
) -> dict[str, Any]:
events = self.repository.list_audit_events(target=target, correlation_id=correlation_id)
return {"items": [event.to_dict() for event in events], "count": len(events)}
def evaluate_policy(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
try:
decision = self.policy_gateway.authorize(
context,
payload["action"],
payload["resource"],
resource_metadata=dict(payload.get("resource_metadata", {})),
)
except Exception as exc:
decision = PolicyDecision.fail_closed(
context.actor.id,
payload.get("action", "unknown"),
payload.get("resource", "unknown"),
reason=str(exc),
context={"gateway_error": type(exc).__name__},
)
return decision.to_dict()
def ingestion_capabilities(self) -> dict[str, Any]:
service = self.ingestion_service()
return {
"connectors": service.connector_capabilities(),
"extractors": service.extractor_capabilities(),
}
def start_ingestion_job(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
service = self.ingestion_service()
mode = payload.get("mode", "file")
classification = _optional_classification(payload.get("classification"))
identity_policy = payload.get("identity_policy", IngestionIdentityPolicy.SOURCE_LOCATION.value)
if mode == "directory":
job = service.ingest_directory(
payload["path"],
context,
recursive=bool(payload.get("recursive", True)),
classification=classification,
identity_policy=identity_policy,
skip_unchanged=bool(payload.get("skip_unchanged", True)),
)
return _ingestion_job_envelope(job)
if mode != "file":
raise ValidationError(
"Unsupported ingestion mode",
details={"mode": mode, "supported": ["file", "directory"]},
)
result = service.ingest_file(
payload["path"],
context,
asset_id=payload.get("asset_id"),
title=payload.get("title"),
classification=classification,
idempotency_key=payload.get("idempotency_key"),
identity_policy=identity_policy,
skip_unchanged=bool(payload.get("skip_unchanged", True)),
)
return _ingestion_result_envelope(result)
def get_ingestion_job(self, job_id: str) -> dict[str, Any]:
return _ingestion_job_envelope(self.ingestion_service().get_job(job_id))
def list_ingestion_jobs(self, *, status: str | None = None) -> dict[str, Any]:
parsed_status = _enum_filter(IngestionJobStatus, status, "ingestion job status")
jobs = self.ingestion_service().list_jobs(status=parsed_status)
return {"items": [_ingestion_job_envelope(job) for job in jobs], "count": len(jobs)}
def refresh_retrieval_index(self) -> dict[str, Any]:
return self.retrieval_service().refresh_index().to_dict()
def query_assets(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
service = self.retrieval_service()
service.refresh_index()
return service.query_assets(_asset_query_request(payload), context).to_dict()
def query_context_entities(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return self.retrieval_service().query_context_entities(
_context_entity_query_request(payload),
context,
).to_dict()
def query_relationships(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return self.retrieval_service().query_relationships(
_relationship_query_request(payload),
context,
).to_dict()
def record_retrieval_feedback(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return self.retrieval_service().record_feedback(
RetrievalFeedbackRequest(
label=payload["label"],
query=dict(payload.get("query", {})),
result_ref=dict(payload.get("result_ref", {})),
notes=payload.get("notes"),
metadata=dict(payload.get("metadata", {})),
),
context,
).to_dict()
def list_retrieval_feedback(
self,
*,
correlation_id: str | None = None,
label: str | None = None,
) -> dict[str, Any]:
parsed_label = _enum_filter(RetrievalFeedbackLabel, label, "retrieval feedback label")
records = self.retrieval_service().list_feedback(correlation_id=correlation_id, label=parsed_label)
return {"items": [record.to_dict() for record in records], "count": len(records)}
def retrieval_quality_metrics(self) -> dict[str, Any]:
return self.retrieval_service().quality_metrics().to_dict()
def list_transformation_operations(self) -> dict[str, Any]:
operations = self.transformation_service().list_operations()
return {"items": [operation.to_dict() for operation in operations], "count": len(operations)}
def execute_transformation(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
result = self.transformation_service().execute_transformation(
_transformation_request(payload),
context,
)
return _transformation_result_envelope(result)
def get_transformation_run(self, run_id: str) -> dict[str, Any]:
return _transformation_run_envelope(self.transformation_service().get_run(run_id))
def list_transformation_runs(
self,
*,
status: str | None = None,
operation_id: str | None = None,
) -> dict[str, Any]:
parsed_status = _enum_filter(TransformationRunStatus, status, "transformation run status")
runs = self.transformation_service().list_runs(status=parsed_status, operation_id=operation_id)
return {"items": [_transformation_run_envelope(run) for run in runs], "count": len(runs)}
def retry_transformation_run(self, run_id: str, context: OperationContext) -> dict[str, Any]:
return _transformation_result_envelope(self.transformation_service().retry_run(run_id, context))
def cancel_transformation_run(
self,
run_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
run = self.transformation_service().cancel_run(run_id, context, reason=payload.get("reason"))
return _transformation_run_envelope(run)
def register_workflow_template(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return self.workflow_service().register_template(_workflow_template(payload), context).to_dict()
def get_workflow_template(self, template_id: str, *, version: str | None = None) -> dict[str, Any]:
return self.workflow_service().get_template(template_id, version=version).to_dict()
def list_workflow_templates(self, *, template_id: str | None = None) -> dict[str, Any]:
templates = self.workflow_service().list_templates(template_id=template_id)
return {"items": [template.to_dict() for template in templates], "count": len(templates)}
def queue_workflow_run(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return _workflow_result_envelope(
self.workflow_service().queue_template(_workflow_invocation(payload), context)
)
def invoke_workflow_run(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
return _workflow_result_envelope(
self.workflow_service().invoke_template(_workflow_invocation(payload), context)
)
def get_workflow_run(self, run_id: str) -> dict[str, Any]:
return _workflow_run_envelope(self.repository.get_workflow_run(run_id))
def list_workflow_runs(
self,
*,
status: str | None = None,
template_id: str | None = None,
) -> dict[str, Any]:
parsed_status = _enum_filter(WorkflowRunStatus, status, "workflow run status")
runs = self.repository.list_workflow_runs(status=parsed_status, template_id=template_id)
return {"items": [_workflow_run_envelope(run) for run in runs], "count": len(runs)}
def resume_workflow_run(self, run_id: str, context: OperationContext) -> dict[str, Any]:
return _workflow_result_envelope(self.workflow_service().resume_run(run_id, context))
def retry_workflow_run(self, run_id: str, context: OperationContext) -> dict[str, Any]:
return _workflow_result_envelope(self.workflow_service().retry_run(run_id, context))
def cancel_workflow_run(
self,
run_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
run = self.workflow_service().cancel_run(run_id, context, reason=payload.get("reason"))
return _workflow_run_envelope(run)
def reconstruct_workflow_run(self, run_id: str) -> dict[str, Any]:
return self.workflow_service().reconstruct_run(run_id).to_dict()
def list_workflow_review_tasks(
self,
*,
status: str | None = WorkflowReviewStatus.OPEN.value,
workflow_run_id: str | None = None,
) -> dict[str, Any]:
parsed_status = _enum_filter(WorkflowReviewStatus, status, "workflow review status")
reviews = self.workflow_service().list_review_tasks(
status=parsed_status,
workflow_run_id=workflow_run_id,
)
return {"items": [review.to_dict() for review in reviews], "count": len(reviews)}
def list_workflow_exceptions(
self,
*,
status: str | None = WorkflowExceptionStatus.OPEN.value,
kind: str | None = None,
workflow_run_id: str | None = None,
) -> dict[str, Any]:
parsed_status = _enum_filter(WorkflowExceptionStatus, status, "workflow exception status")
parsed_kind = _enum_filter(WorkflowExceptionKind, kind, "workflow exception kind")
exceptions = self.workflow_service().list_exception_queue(
status=parsed_status,
kind=parsed_kind,
workflow_run_id=workflow_run_id,
)
return {"items": [exception.to_dict() for exception in exceptions], "count": len(exceptions)}
def record_workflow_review_decision(
self,
run_id: str,
review_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
decision = payload.get("decision", WorkflowReviewDecisionType.CONTINUE.value)
return _workflow_result_envelope(
self.workflow_service().record_review_decision(
run_id,
review_id,
decision,
context,
note=payload.get("note", ""),
correction=dict(payload.get("correction", {})),
)
)
def list_agent_operations(self) -> dict[str, Any]:
return {"items": [dict(item) for item in AGENT_OPERATION_CATALOG], "count": len(AGENT_OPERATION_CATALOG)}
def get_agent_operation(self, operation_id: str) -> dict[str, Any]:
return dict(_agent_operation(operation_id))
def execute_agent_operation(
self,
operation_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
operation = _agent_operation(operation_id)
dry_run = bool(payload.get("dry_run", False))
operation_payload = dict(payload.get("payload", payload))
operation_payload.pop("dry_run", None)
decision = self._authorize_agent_operation(operation, operation_payload, context)
effect = decision.effect
if effect == PolicyEffect.REQUIRE_REVIEW:
event = self._audit_agent_operation(
operation,
AuditOutcome.REVIEW_REQUIRED,
context,
decision,
details={"phase": "review_required", "payload_keys": sorted(operation_payload)},
)
return _agent_review_required_envelope(operation, decision, event, context)
if effect == PolicyEffect.DRY_RUN_ONLY and not dry_run:
event = self._audit_agent_operation(
operation,
AuditOutcome.DRY_RUN,
context,
decision,
details={"phase": "dry_run_required", "payload_keys": sorted(operation_payload)},
)
return _agent_dry_run_required_envelope(operation, decision, event, context)
queued_event = self._audit_agent_operation(
operation,
AuditOutcome.DRY_RUN if dry_run else AuditOutcome.SUCCESS,
context,
decision,
details={
"phase": "accepted",
"dry_run": dry_run,
"payload_keys": sorted(operation_payload),
},
)
if dry_run:
return {
"operation_id": operation_id,
"dry_run": True,
"success": True,
"would_execute": operation,
"correlation_id": context.correlation_id,
"policy_decision": decision.to_dict(),
"audit_event": queued_event.to_dict(),
}
try:
result = self._dispatch_agent_operation(operation_id, operation_payload, context)
except Exception as exc:
failed_event = self._audit_agent_operation(
operation,
AuditOutcome.FAILED,
context,
decision,
details={
"phase": "failed",
"error_type": type(exc).__name__,
"payload_keys": sorted(operation_payload),
},
)
if isinstance(exc, KontextualError):
exc.details.setdefault("agent_audit_event_id", failed_event.event_id)
raise
completed_event = self._audit_agent_operation(
operation,
AuditOutcome.SUCCESS,
context,
decision,
details={
"phase": "completed",
"result_keys": sorted(result) if isinstance(result, dict) else [],
},
)
return {
"operation_id": operation_id,
"dry_run": False,
"success": True,
"correlation_id": context.correlation_id,
"result": result,
"policy_decision": decision.to_dict(),
"audit_event": completed_event.to_dict(),
}
def _dispatch_agent_operation(
self,
operation_id: str,
payload: dict[str, Any],
context: OperationContext,
) -> dict[str, Any]:
if operation_id == "inspect_asset":
return self.get_asset(payload["asset_id"])
if operation_id == "retrieve_asset":
return self._asset_bundle(payload["asset_id"])
if operation_id == "search_assets":
return self.query_assets(dict(payload.get("query", {})), context)
if operation_id == "assemble_context":
query_result = self.query_assets(dict(payload.get("query", {})), context)
return {
"context_preview": {
"intent": payload.get("intent", "Support a bounded agent task with source-grounded context."),
"instructions": payload.get("instructions", ""),
"constraints": dict(payload.get("constraints", {})),
"correlation_id": query_result["correlation_id"],
"source_grounded": True,
"items": query_result["results"],
"result_count": query_result["result_count"],
"total": query_result["total"],
}
}
if operation_id == "enrich_metadata":
return self.add_metadata_record(payload["asset_id"], dict(payload["metadata"]), context)
if operation_id == "classify_asset":
request = {
"operation_id": "classify",
"source_asset_ids": [payload["asset_id"]],
"parameters": dict(payload.get("parameters", {})),
"metadata": dict(payload.get("metadata", {})),
}
return self.execute_transformation(request, context)
if operation_id == "transform_asset":
return self.execute_transformation(dict(payload["transformation"]), context)
if operation_id == "invoke_workflow":
return self.invoke_workflow_run(dict(payload["workflow"]), context)
if operation_id == "submit_review":
return self.record_workflow_review_decision(
payload["run_id"],
payload["review_id"],
payload,
context,
)
if operation_id == "report_result":
return self._agent_report(payload, context)
raise ValidationError("Unsupported agent operation", details={"operation_id": operation_id})
def _authorize_agent_operation(
self,
operation: dict[str, Any],
payload: dict[str, Any],
context: OperationContext,
) -> PolicyDecision:
action = f"agent.operation.{operation['operation_id']}"
resource = f"agent_operation:{operation['operation_id']}"
try:
decision = self.policy_gateway.authorize(
context,
action,
resource,
resource_metadata={
"operation": operation,
"payload_keys": sorted(payload),
"dry_run_supported": operation["dry_run_supported"],
},
)
except Exception as exc:
decision = PolicyDecision.fail_closed(
context.actor.id,
action,
resource,
reason=str(exc) or "Agent operation policy gateway failed",
context={"gateway_error": type(exc).__name__},
)
if not decision.allowed and decision.effect not in (
PolicyEffect.REQUIRE_REVIEW,
PolicyEffect.DRY_RUN_ONLY,
):
event = self._audit_agent_operation(operation, AuditOutcome.DENIED, context, decision)
raise AuthorizationError(
"Operation denied by policy",
details={
"action": action,
"resource": resource,
"correlation_id": context.correlation_id,
"agent_audit_event_id": event.event_id,
"policy_decision": decision.to_dict(),
},
)
return decision
def _audit_agent_operation(
self,
operation: dict[str, Any],
outcome: AuditOutcome,
context: OperationContext,
policy_decision: PolicyDecision,
*,
details: dict[str, Any] | None = None,
) -> AuditEvent:
event = AuditEvent.from_context(
operation["audit_operation"],
f"agent_operation:{operation['operation_id']}",
outcome,
context,
policy_decision=policy_decision,
details=details,
)
return self.repository.save_audit_event(event)
def _asset_bundle(self, asset_id: str) -> dict[str, Any]:
asset = self.repository.get_asset(asset_id)
metadata_records = self.repository.list_metadata_records(asset_id)
representations = self.repository.list_representations(asset_id=asset_id)
relationships = self.repository.list_relationships(source_id=asset_id)
return {
"asset": asset.to_dict(),
"metadata_records": [record.to_dict() for record in metadata_records],
"representations": [representation.to_dict() for representation in representations],
"relationships": [relationship.to_dict() for relationship in relationships],
"source_grounded": bool(asset.source_refs or representations),
}
def _agent_report(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
if not payload.get("summary"):
raise ValidationError("Agent result report requires a summary", details={"required": ["summary"]})
decision = PolicyDecision.allow(
context.actor.id,
"agent.operation.report_result.record",
f"agent:{context.actor.id}",
context={"correlation_id": context.correlation_id},
)
event = AuditEvent.from_context(
"agent.report.recorded",
f"agent:{context.actor.id}",
AuditOutcome.SUCCESS,
context,
policy_decision=decision,
details={
"summary": payload["summary"],
"result_ref": dict(payload.get("result_ref", {})),
"metadata": dict(payload.get("metadata", {})),
},
)
saved = self.repository.save_audit_event(event)
return {
"summary": payload["summary"],
"result_ref": dict(payload.get("result_ref", {})),
"metadata": dict(payload.get("metadata", {})),
"audit_event": saved.to_dict(),
}
def context_package_schema(self) -> dict[str, Any]:
return {
"kind": "kontextual.context_package",
"version": "1",
"required": ["query"],
"optional": [
"package_id",
"title",
"intent",
"instructions",
"constraints",
"external_memory_refs",
"metadata",
"format",
],
"formats": ["kontextual", "markitect"],
"source_grounding": ["source_refs", "representations", "snippets", "relationships", "metadata_records"],
"memory_boundary": "external_memory_refs are opaque pointers; memory graph contents are not embedded",
}
def assemble_context_package(self, payload: dict[str, Any], context: OperationContext) -> dict[str, Any]:
query = dict(payload.get("query", {}))
query.setdefault("include_snippets", True)
query.setdefault("include_relationships", True)
query.setdefault("max_snippets", 5)
constraints = dict(payload.get("constraints", {}))
decision = self._authorize_context_package(query, constraints, context)
query_result = self.query_assets(query, context)
package = _context_package_payload(
payload,
query_result,
context,
constraints=constraints,
)
event = self._audit_context_package(package, context, decision)
package["audit_event"] = event.to_dict()
package["policy_decision"] = decision.to_dict()
return package
def _authorize_context_package(
self,
query: dict[str, Any],
constraints: dict[str, Any],
context: OperationContext,
) -> PolicyDecision:
try:
decision = self.policy_gateway.authorize(
context,
"context_package.assemble",
"context_package:new",
resource_metadata={
"query": query,
"constraints": constraints,
"external_memory_refs": "opaque",
},
)
except Exception as exc:
decision = PolicyDecision.fail_closed(
context.actor.id,
"context_package.assemble",
"context_package:new",
reason=str(exc) or "Context package policy gateway failed",
context={"gateway_error": type(exc).__name__},
)
if not decision.allowed:
event = AuditEvent.from_context(
"context_package.assemble",
"context_package:new",
AuditOutcome.DENIED,
context,
policy_decision=decision,
details={"query": query, "constraints": constraints},
)
saved = self.repository.save_audit_event(event)
raise AuthorizationError(
"Operation denied by policy",
details={
"action": "context_package.assemble",
"resource": "context_package:new",
"correlation_id": context.correlation_id,
"audit_event_id": saved.event_id,
"policy_decision": decision.to_dict(),
},
)
return decision
def _audit_context_package(
self,
package: dict[str, Any],
context: OperationContext,
decision: PolicyDecision,
) -> AuditEvent:
event = AuditEvent.from_context(
"context_package.assemble",
f"context_package:{package['package_id']}",
AuditOutcome.SUCCESS,
context,
policy_decision=decision,
details={
"result_count": package["result_count"],
"source_grounded": package["source_grounded"],
"format": package["format"],
"external_memory_ref_count": len(package["external_memory_refs"]),
},
)
return self.repository.save_audit_event(event)
2026-05-06 19:30:49 +02:00
def create_app(runtime: ServiceRuntime | None = None):
try:
from fastapi import Depends, FastAPI, Header, HTTPException, Query
from fastapi.responses import JSONResponse
2026-05-06 19:30:49 +02:00
except ImportError as exc: # pragma: no cover - exercised when optional extra is absent
raise RuntimeError(
"FastAPI service dependencies are not installed. Install kontextual-engine[service]."
) from exc
runtime = runtime or ServiceRuntime()
app = FastAPI(
title="Kontextual Engine Service API",
version=OPENAPI_VERSION,
openapi_url="/openapi.json",
docs_url="/docs",
redoc_url="/redoc",
)
app.state.kontextual_runtime = runtime
@app.exception_handler(NotFoundError)
async def not_found_error_handler(_request, exc: NotFoundError) -> JSONResponse:
return JSONResponse(status_code=404, content=_error_payload(exc))
@app.exception_handler(AuthorizationError)
async def authorization_error_handler(_request, exc: AuthorizationError) -> JSONResponse:
return JSONResponse(status_code=403, content=_authorization_error_payload(exc))
@app.exception_handler(ValidationError)
async def validation_error_handler(_request, exc: ValidationError) -> JSONResponse:
return JSONResponse(status_code=422, content=_error_payload(exc))
@app.exception_handler(KontextualError)
async def kontextual_error_handler(_request, exc: KontextualError) -> JSONResponse:
return JSONResponse(status_code=400, content=_error_payload(exc))
2026-05-06 19:30:49 +02:00
@app.get("/health", tags=["system"])
def health() -> dict[str, Any]:
return runtime.health()
@app.get("/ready", tags=["system"])
def ready() -> dict[str, Any]:
return runtime.readiness()
@app.get("/version", tags=["system"])
def version() -> dict[str, Any]:
return runtime.version()
prefix = f"/api/{runtime.api_version}"
@app.get(f"{prefix}/health", tags=["system"])
def versioned_health() -> dict[str, Any]:
return runtime.health()
@app.get(f"{prefix}/ready", tags=["system"])
def versioned_ready() -> dict[str, Any]:
return runtime.readiness()
@app.get(f"{prefix}/version", tags=["system"])
def versioned_version() -> dict[str, Any]:
return runtime.version()
def context_from_headers(
x_actor_id: str | None = Header(None),
x_actor_type: str | None = Header(None),
x_actor_display_name: str | None = Header(None),
x_actor_external_ref: str | None = Header(None),
x_actor_groups: str | None = Header(None),
x_correlation_id: str | None = Header(None),
x_delegated_actor_id: str | None = Header(None),
x_delegated_actor_type: str | None = Header(None),
x_delegated_actor_display_name: str | None = Header(None),
x_delegated_actor_external_ref: str | None = Header(None),
x_delegated_actor_groups: str | None = Header(None),
x_agent_id: str | None = Header(None),
x_agent_name: str | None = Header(None),
x_agent_run_id: str | None = Header(None),
x_agent_tool: str | None = Header(None),
x_request_scope: str | None = Header(None),
x_policy_scope: str | None = Header(None),
) -> OperationContext:
actor_id = x_actor_id or x_agent_id or "api-user"
actor_type = x_actor_type or ("ai_agent" if x_agent_id else "human")
return runtime.operation_context(
actor_id=actor_id,
actor_type=actor_type,
display_name=x_actor_display_name,
external_ref=x_actor_external_ref,
correlation_id=x_correlation_id,
groups=_split_header_list(x_actor_groups),
delegated_actor_id=x_delegated_actor_id,
delegated_actor_type=x_delegated_actor_type or "human",
delegated_actor_display_name=x_delegated_actor_display_name,
delegated_actor_external_ref=x_delegated_actor_external_ref,
delegated_actor_groups=_split_header_list(x_delegated_actor_groups),
request_scope=_json_header(x_request_scope, "X-Request-Scope"),
policy_scope=_json_header(x_policy_scope, "X-Policy-Scope"),
agent_id=x_agent_id,
agent_name=x_agent_name,
agent_run_id=x_agent_run_id,
agent_tool=x_agent_tool,
)
def response(callable_obj, *args: Any, **kwargs: Any) -> Any:
try:
return callable_obj(*args, **kwargs)
except NotFoundError as exc:
raise HTTPException(status_code=404, detail=_error_payload(exc)) from exc
except AuthorizationError as exc:
raise HTTPException(status_code=403, detail=_authorization_error_payload(exc)) from exc
except ValidationError as exc:
raise HTTPException(status_code=422, detail=_error_payload(exc)) from exc
except KontextualError as exc:
raise HTTPException(status_code=400, detail=_error_payload(exc)) from exc
except (KeyError, TypeError, ValueError) as exc:
raise HTTPException(
status_code=422,
detail={
"code": "kontextual.validation",
"message": "Invalid request payload",
"details": {"error_type": type(exc).__name__, "message": str(exc)},
},
) from exc
@app.get(f"{prefix}/context", tags=["context"])
def current_context(
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return context.to_dict()
@app.post(f"{prefix}/assets", tags=["assets"])
def create_asset(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.create_asset, payload, context)
@app.get(f"{prefix}/assets", tags=["assets"])
def list_assets(
lifecycle: str | None = Query(None),
asset_type: str | None = Query(None),
sensitivity: str | None = Query(None),
owner: str | None = Query(None),
topic: str | None = Query(None),
review_state: str | None = Query(None),
) -> dict[str, Any]:
return response(
runtime.list_assets,
lifecycle=lifecycle,
asset_type=asset_type,
sensitivity=sensitivity,
owner=owner,
topic=topic,
review_state=review_state,
)
@app.get(f"{prefix}/assets/{{asset_id}}", tags=["assets"])
def get_asset(asset_id: str) -> dict[str, Any]:
return response(runtime.get_asset, asset_id)
@app.post(f"{prefix}/assets/{{asset_id}}/metadata", tags=["metadata"])
def add_metadata(
asset_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.add_metadata_record, asset_id, payload, context)
@app.get(f"{prefix}/assets/{{asset_id}}/metadata", tags=["metadata"])
def list_metadata(asset_id: str) -> dict[str, Any]:
return response(runtime.list_metadata_records, asset_id)
@app.post(f"{prefix}/assets/{{asset_id}}/lifecycle", tags=["assets"])
def transition_lifecycle(
asset_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.transition_lifecycle, asset_id, payload, context)
@app.post(f"{prefix}/relationships", tags=["relationships"])
def create_relationship(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.create_relationship, payload, context)
@app.get(f"{prefix}/relationships", tags=["relationships"])
def list_relationships(
source_id: str | None = Query(None),
target_id: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_relationships, source_id=source_id, target_id=target_id)
@app.get(f"{prefix}/audit/events", tags=["audit"])
def list_audit_events(
target: str | None = Query(None),
correlation_id: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_audit_events, target=target, correlation_id=correlation_id)
@app.post(f"{prefix}/policy/evaluate", tags=["policy"])
def evaluate_policy(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.evaluate_policy, payload, context)
@app.get(f"{prefix}/ingestion/capabilities", tags=["ingestion"])
def ingestion_capabilities() -> dict[str, Any]:
return response(runtime.ingestion_capabilities)
@app.post(f"{prefix}/ingestion/jobs", tags=["ingestion"])
def start_ingestion_job(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.start_ingestion_job, payload, context)
@app.get(f"{prefix}/ingestion/jobs", tags=["ingestion"])
def list_ingestion_jobs(status: str | None = Query(None)) -> dict[str, Any]:
return response(runtime.list_ingestion_jobs, status=status)
@app.get(f"{prefix}/ingestion/jobs/{{job_id}}", tags=["ingestion"])
def get_ingestion_job(job_id: str) -> dict[str, Any]:
return response(runtime.get_ingestion_job, job_id)
@app.post(f"{prefix}/retrieval/index/refresh", tags=["retrieval"])
def refresh_retrieval_index() -> dict[str, Any]:
return response(runtime.refresh_retrieval_index)
@app.post(f"{prefix}/retrieval/assets", tags=["retrieval"])
def query_assets(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.query_assets, payload, context)
@app.post(f"{prefix}/retrieval/context-entities", tags=["retrieval"])
def query_context_entities(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.query_context_entities, payload, context)
@app.post(f"{prefix}/retrieval/relationships", tags=["retrieval"])
def query_retrieval_relationships(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.query_relationships, payload, context)
@app.post(f"{prefix}/retrieval/feedback", tags=["retrieval"])
def record_retrieval_feedback(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.record_retrieval_feedback, payload, context)
@app.get(f"{prefix}/retrieval/feedback", tags=["retrieval"])
def list_retrieval_feedback(
correlation_id: str | None = Query(None),
label: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_retrieval_feedback, correlation_id=correlation_id, label=label)
@app.get(f"{prefix}/retrieval/quality", tags=["retrieval"])
def retrieval_quality_metrics() -> dict[str, Any]:
return response(runtime.retrieval_quality_metrics)
@app.get(f"{prefix}/transformations/operations", tags=["transformations"])
def list_transformation_operations() -> dict[str, Any]:
return response(runtime.list_transformation_operations)
@app.post(f"{prefix}/transformations/runs", tags=["transformations"])
def execute_transformation(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.execute_transformation, payload, context)
@app.get(f"{prefix}/transformations/runs", tags=["transformations"])
def list_transformation_runs(
status: str | None = Query(None),
operation_id: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_transformation_runs, status=status, operation_id=operation_id)
@app.get(f"{prefix}/transformations/runs/{{run_id}}", tags=["transformations"])
def get_transformation_run(run_id: str) -> dict[str, Any]:
return response(runtime.get_transformation_run, run_id)
@app.post(f"{prefix}/transformations/runs/{{run_id}}/retry", tags=["transformations"])
def retry_transformation_run(
run_id: str,
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.retry_transformation_run, run_id, context)
@app.post(f"{prefix}/transformations/runs/{{run_id}}/cancel", tags=["transformations"])
def cancel_transformation_run(
run_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.cancel_transformation_run, run_id, payload, context)
@app.post(f"{prefix}/workflows/templates", tags=["workflows"])
def register_workflow_template(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.register_workflow_template, payload, context)
@app.get(f"{prefix}/workflows/templates", tags=["workflows"])
def list_workflow_templates(template_id: str | None = Query(None)) -> dict[str, Any]:
return response(runtime.list_workflow_templates, template_id=template_id)
@app.get(f"{prefix}/workflows/templates/{{template_id}}", tags=["workflows"])
def get_workflow_template(
template_id: str,
version: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.get_workflow_template, template_id, version=version)
@app.post(f"{prefix}/workflows/runs", tags=["workflows"])
def invoke_workflow_run(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.invoke_workflow_run, payload, context)
@app.post(f"{prefix}/workflows/runs/queue", tags=["workflows"])
def queue_workflow_run(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.queue_workflow_run, payload, context)
@app.get(f"{prefix}/workflows/runs", tags=["workflows"])
def list_workflow_runs(
status: str | None = Query(None),
template_id: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_workflow_runs, status=status, template_id=template_id)
@app.get(f"{prefix}/workflows/runs/{{run_id}}", tags=["workflows"])
def get_workflow_run(run_id: str) -> dict[str, Any]:
return response(runtime.get_workflow_run, run_id)
@app.post(f"{prefix}/workflows/runs/{{run_id}}/resume", tags=["workflows"])
def resume_workflow_run(
run_id: str,
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.resume_workflow_run, run_id, context)
@app.post(f"{prefix}/workflows/runs/{{run_id}}/retry", tags=["workflows"])
def retry_workflow_run(
run_id: str,
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.retry_workflow_run, run_id, context)
@app.post(f"{prefix}/workflows/runs/{{run_id}}/cancel", tags=["workflows"])
def cancel_workflow_run(
run_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.cancel_workflow_run, run_id, payload, context)
@app.get(f"{prefix}/workflows/runs/{{run_id}}/reconstruction", tags=["workflows"])
def reconstruct_workflow_run(run_id: str) -> dict[str, Any]:
return response(runtime.reconstruct_workflow_run, run_id)
@app.get(f"{prefix}/workflows/reviews", tags=["workflows"])
def list_workflow_review_tasks(
status: str | None = Query(WorkflowReviewStatus.OPEN.value),
workflow_run_id: str | None = Query(None),
) -> dict[str, Any]:
return response(runtime.list_workflow_review_tasks, status=status, workflow_run_id=workflow_run_id)
@app.get(f"{prefix}/workflows/exceptions", tags=["workflows"])
def list_workflow_exceptions(
status: str | None = Query(WorkflowExceptionStatus.OPEN.value),
kind: str | None = Query(None),
workflow_run_id: str | None = Query(None),
) -> dict[str, Any]:
return response(
runtime.list_workflow_exceptions,
status=status,
kind=kind,
workflow_run_id=workflow_run_id,
)
@app.post(f"{prefix}/workflows/runs/{{run_id}}/reviews/{{review_id}}/decision", tags=["workflows"])
def record_workflow_review_decision(
run_id: str,
review_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.record_workflow_review_decision, run_id, review_id, payload, context)
@app.get(f"{prefix}/agents/operations", tags=["agents"])
def list_agent_operations() -> dict[str, Any]:
return response(runtime.list_agent_operations)
@app.get(f"{prefix}/agents/operations/{{operation_id}}", tags=["agents"])
def get_agent_operation(operation_id: str) -> dict[str, Any]:
return response(runtime.get_agent_operation, operation_id)
@app.post(f"{prefix}/agents/operations/{{operation_id}}", tags=["agents"])
def execute_agent_operation(
operation_id: str,
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.execute_agent_operation, operation_id, payload, context)
@app.get(f"{prefix}/context-packages/schema", tags=["context-packages"])
def context_package_schema() -> dict[str, Any]:
return response(runtime.context_package_schema)
@app.post(f"{prefix}/context-packages", tags=["context-packages"])
def assemble_context_package(
payload: dict[str, Any],
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
return response(runtime.assemble_context_package, payload, context)
2026-05-06 19:30:49 +02:00
return app
def _agent_metadata(
*,
agent_id: str | None,
agent_name: str | None,
agent_run_id: str | None,
agent_tool: str | None,
) -> dict[str, Any]:
return {
key: value
for key, value in {
"agent_id": agent_id,
"agent_name": agent_name,
"agent_run_id": agent_run_id,
"agent_tool": agent_tool,
}.items()
if value
}
def _agent_operation(operation_id: str) -> dict[str, Any]:
for operation in AGENT_OPERATION_CATALOG:
if operation["operation_id"] == operation_id:
return operation
raise ValidationError(
"Unsupported agent operation",
details={
"operation_id": operation_id,
"supported": [operation["operation_id"] for operation in AGENT_OPERATION_CATALOG],
},
)
def _agent_review_required_envelope(
operation: dict[str, Any],
decision: PolicyDecision,
event: AuditEvent,
context: OperationContext,
) -> dict[str, Any]:
return {
"operation_id": operation["operation_id"],
"dry_run": False,
"success": False,
"review_required": True,
"correlation_id": context.correlation_id,
"review": {
"reason": decision.reason,
"obligations": dict(decision.obligations),
"required_permissions": list(operation["required_permissions"]),
},
"policy_decision": decision.to_dict(),
"audit_event": event.to_dict(),
}
def _agent_dry_run_required_envelope(
operation: dict[str, Any],
decision: PolicyDecision,
event: AuditEvent,
context: OperationContext,
) -> dict[str, Any]:
return {
"operation_id": operation["operation_id"],
"dry_run": False,
"success": False,
"dry_run_required": True,
"correlation_id": context.correlation_id,
"reason": decision.reason,
"policy_decision": decision.to_dict(),
"audit_event": event.to_dict(),
}
def _context_package_payload(
request_payload: dict[str, Any],
query_result: dict[str, Any],
context: OperationContext,
*,
constraints: dict[str, Any],
) -> dict[str, Any]:
package_format = request_payload.get("format", "kontextual")
if package_format not in {"kontextual", "markitect"}:
raise ValidationError(
"Unsupported context package format",
details={"format": package_format, "supported": ["kontextual", "markitect"]},
)
items = [_context_package_item(item) for item in query_result.get("results", ())]
package = {
"kind": "kontextual.context_package",
"version": "1",
"package_id": request_payload.get("package_id") or new_id("ctxpkg"),
"title": request_payload.get("title", "Kontextual Context Package"),
"intent": request_payload.get("intent", "Provide bounded, source-grounded context."),
"instructions": request_payload.get("instructions", ""),
"format": package_format,
"correlation_id": context.correlation_id,
"query": query_result.get("query", {}),
"result_count": query_result.get("result_count", len(items)),
"total": query_result.get("total", len(items)),
"source_grounded": bool(items) and all(item["source_refs"] or item["snippets"] for item in items),
"policy_constraints": constraints,
"external_memory_refs": [
_opaque_memory_ref(item) for item in request_payload.get("external_memory_refs", ())
],
"items": items,
"metadata": dict(request_payload.get("metadata", {})),
}
if package_format == "markitect":
package["markitect_payload"] = _markitect_context_payload(package)
return package
def _context_package_item(result: dict[str, Any]) -> dict[str, Any]:
return {
"asset_id": result["asset_id"],
"title": result.get("title"),
"classification": dict(result.get("classification", {})),
"lifecycle": result.get("lifecycle"),
"source_refs": list(result.get("source_refs", ())),
"snippets": list(result.get("snippets", ())),
"metadata_records": list(result.get("metadata_records", ())),
"relationships": list(result.get("relationships", ())),
"representations": [
{
"representation_id": item.get("representation_id"),
"kind": item.get("kind"),
"media_type": item.get("media_type"),
"source_ref_id": item.get("source_ref_id"),
"storage_ref": item.get("storage_ref"),
"producer": item.get("producer"),
"metadata": {
key: value
for key, value in dict(item.get("metadata", {})).items()
if key in {"extractor", "producer", "normalized_hash", "search_text_length"}
},
}
for item in result.get("representations", ())
],
"relevance": dict(result.get("relevance", {})),
}
def _opaque_memory_ref(data: dict[str, Any]) -> dict[str, Any]:
ref_id = data.get("ref_id") or data.get("id") or data.get("uri")
if not ref_id:
raise ValidationError(
"External memory reference requires ref_id, id, or uri",
details={"required": ["ref_id"]},
)
return {
"ref_id": str(ref_id),
"system": data.get("system", "phase-memory"),
"kind": data.get("kind", "memory_ref"),
"opaque": True,
"metadata": dict(data.get("metadata", {})),
}
def _markitect_context_payload(package: dict[str, Any]) -> dict[str, Any]:
return {
"kind": "markitect.context_package",
"version": "1",
"id": package["package_id"],
"title": package["title"],
"intent": package["intent"],
"instructions": package["instructions"],
"policy_constraints": dict(package["policy_constraints"]),
"external_memory_refs": list(package["external_memory_refs"]),
"items": [
{
"asset_id": item["asset_id"],
"title": item["title"],
"source_refs": list(item["source_refs"]),
"snippets": list(item["snippets"]),
"metadata_records": list(item["metadata_records"]),
"relationships": list(item["relationships"]),
}
for item in package["items"]
],
"adapter_boundary": "markdown rendering and selector semantics are delegated to markitect-tool",
}
def _split_header_list(value: str | None) -> list[str] | None:
if value is None:
return None
return [item.strip() for item in value.split(",") if item.strip()]
def _json_header(value: str | None, header_name: str) -> dict[str, Any] | None:
if value is None or not value.strip():
return None
try:
parsed = json.loads(value)
except json.JSONDecodeError as exc:
raise ValidationError(
"Header must contain a JSON object",
details={"header": header_name, "message": str(exc)},
) from exc
if not isinstance(parsed, dict):
raise ValidationError(
"Header must contain a JSON object",
details={"header": header_name, "actual_type": type(parsed).__name__},
)
return parsed
def _optional_classification(data: dict[str, Any] | None) -> Classification | None:
return Classification.from_dict(data) if data else None
def _enum_filter(enum_type: Any, value: Any, label: str) -> Any:
if value is None or isinstance(value, enum_type):
return value
try:
return enum_type(value)
except ValueError as exc:
raise ValidationError(
f"Unsupported {label}",
details={"value": value, "supported": [item.value for item in enum_type]},
) from exc
def _dataclass_request(request_type: Any, data: dict[str, Any]) -> Any:
try:
return request_type(**data)
except TypeError as exc:
raise ValidationError(
"Invalid request payload",
details={"request_type": request_type.__name__, "message": str(exc)},
) from exc
def _asset_query_request(data: dict[str, Any]) -> AssetQueryRequest:
payload = dict(data)
if "tags" in payload:
payload["tags"] = tuple(payload["tags"])
return _dataclass_request(AssetQueryRequest, payload)
def _context_entity_query_request(data: dict[str, Any]) -> ContextEntityQueryRequest:
return _dataclass_request(ContextEntityQueryRequest, dict(data))
def _relationship_query_request(data: dict[str, Any]) -> RelationshipQueryRequest:
return _dataclass_request(RelationshipQueryRequest, dict(data))
def _transformation_request(data: dict[str, Any]) -> TransformationRequest:
payload = dict(data)
if "source_asset_ids" in payload:
payload["source_asset_ids"] = tuple(payload["source_asset_ids"])
if "parameters" in payload:
payload["parameters"] = dict(payload["parameters"])
if "metadata" in payload:
payload["metadata"] = dict(payload["metadata"])
return _dataclass_request(TransformationRequest, payload)
def _workflow_template(data: dict[str, Any]) -> WorkflowTemplate:
if "created_at" in data and "updated_at" in data and "template_id" in data:
return WorkflowTemplate.from_dict(data)
kwargs: dict[str, Any] = {
"name": data["name"],
"version": data.get("version", "1"),
"description": data.get("description", ""),
"inputs": tuple(WorkflowInputDefinition.from_dict(item) for item in data.get("inputs", ())),
"steps": tuple(WorkflowStepDefinition.from_dict(item) for item in data.get("steps", ())),
"policy_checks": tuple(dict(item) for item in data.get("policy_checks", ())),
"failure_behavior": data.get("failure_behavior", "fail_workflow"),
"metadata": dict(data.get("metadata", {})),
}
for key in ("template_id", "created_by"):
if data.get(key) is not None:
kwargs[key] = data[key]
return WorkflowTemplate(**kwargs)
def _workflow_invocation(data: dict[str, Any]) -> WorkflowInvocation:
return WorkflowInvocation(
template_id=data["template_id"],
template_version=data.get("template_version"),
inputs=dict(data.get("inputs", {})),
metadata=dict(data.get("metadata", {})),
)
def _ingestion_result_envelope(result: Any) -> dict[str, Any]:
payload = _ingestion_job_envelope(result.job)
payload["action"] = result.action
payload["asset"] = result.asset.to_dict() if result.asset else None
payload["asset_change"] = _asset_change_result(result.asset_change) if result.asset_change else None
return payload
def _ingestion_job_envelope(job: Any) -> dict[str, Any]:
data = job.to_dict()
retry_options = _ingestion_retry_options(job)
data["retry_options"] = retry_options
return {
"job_id": job.job_id,
"status": job.status.value,
"correlation_id": job.correlation_id,
"output_asset_ids": list(job.output_asset_ids),
"failures": [failure.to_dict() for failure in job.failures],
"retry_options": retry_options,
"job": data,
}
def _ingestion_retry_options(job: Any) -> dict[str, Any]:
if job.retry_options:
return dict(job.retry_options)
return {
"retryable": any(failure.retriable for failure in job.failures),
"retryable_failure_codes": [failure.code for failure in job.failures if failure.retriable],
}
def _transformation_result_envelope(result: Any) -> dict[str, Any]:
payload = result.to_dict()
if result.run is not None:
payload["run"] = _transformation_run_envelope(result.run)
payload["correlation_id"] = result.run.correlation_id
payload["retry_options"] = payload["run"]["retry_options"]
return payload
def _transformation_run_envelope(run: Any) -> dict[str, Any]:
data = run.to_dict()
retryable = run.status in (TransformationRunStatus.FAILED, TransformationRunStatus.CANCELED)
cancelable = run.status in (TransformationRunStatus.QUEUED, TransformationRunStatus.RUNNING)
data["retry_options"] = {
"retryable": retryable,
"retry_endpoint": f"/api/v1/transformations/runs/{run.run_id}/retry" if retryable else None,
"cancelable": cancelable,
"cancel_endpoint": f"/api/v1/transformations/runs/{run.run_id}/cancel" if cancelable else None,
}
return data
def _workflow_result_envelope(result: Any) -> dict[str, Any]:
payload = result.to_dict()
payload["run"] = _workflow_run_envelope(result.run)
payload["correlation_id"] = result.run.correlation_id
payload["retry_options"] = payload["run"]["retry_options"]
return payload
def _workflow_run_envelope(run: Any) -> dict[str, Any]:
data = run.to_dict()
retryable = run.status in (
WorkflowRunStatus.FAILED,
WorkflowRunStatus.CANCELED,
WorkflowRunStatus.PARTIALLY_COMPLETED,
)
cancelable = run.status in (WorkflowRunStatus.QUEUED, WorkflowRunStatus.RUNNING, WorkflowRunStatus.WAITING)
data["retry_options"] = {
"retryable": retryable,
"retry_endpoint": f"/api/v1/workflows/runs/{run.run_id}/retry" if retryable else None,
"cancelable": cancelable,
"cancel_endpoint": f"/api/v1/workflows/runs/{run.run_id}/cancel" if cancelable else None,
}
return data
def _metadata_record(data: dict[str, Any]) -> MetadataRecord:
if "record_id" in data and "created_at" in data:
return MetadataRecord.from_dict(data)
return MetadataRecord(
key=data["key"],
value=data.get("value"),
provenance=dict(data.get("provenance", {})),
confidence=data.get("confidence"),
confirmed=bool(data.get("confirmed", False)),
record_id=data.get("record_id") or MetadataRecord(data["key"], data.get("value")).record_id,
)
def _source_reference(data: dict[str, Any]) -> SourceReference:
if "id" in data:
return SourceReference.from_dict(data)
return SourceReference(
source_system=data["source_system"],
path=data.get("path"),
uri=data.get("uri"),
external_id=data.get("external_id"),
checksum=data.get("checksum"),
connector_ref=data.get("connector_ref"),
metadata=dict(data.get("metadata", {})),
)
def _asset_change_result(result: Any) -> dict[str, Any]:
return {
"asset": result.asset.to_dict(),
"version": result.version.to_dict(),
"audit_event": result.audit_event.to_dict(),
"policy_decision": result.policy_decision.to_dict(),
}
def _error_payload(error: KontextualError) -> dict[str, Any]:
return {
"code": error.code,
"message": str(error),
"details": dict(error.details),
}
def _authorization_error_payload(error: AuthorizationError) -> dict[str, Any]:
payload = _error_payload(error)
details = dict(payload.get("details", {}))
decision = details.get("policy_decision")
if isinstance(decision, dict):
details["policy_decision"] = _public_policy_decision(decision)
payload["details"] = details
return payload
def _public_policy_decision(decision: dict[str, Any]) -> dict[str, Any]:
allowed_fields = {
"decision_id",
"effect",
"subject_id",
"action",
"resource",
"reason",
"obligations",
"decided_at",
}
public = {key: value for key, value in decision.items() if key in allowed_fields}
context = decision.get("context")
if isinstance(context, dict):
public_context = {
key: value
for key, value in context.items()
if key not in {"resource_metadata", "protected_metadata", "source_payload"}
}
if public_context:
public["context"] = public_context
return public