Skip to content
Open
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
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ RUN poetry install --only=main
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
ENV L9_LIFECYCLE_HOOK=engine.boot:CodegraphLifecycle
CMD ["uvicorn", "engine.main:app", "--host", "0.0.0.0", "--port", "8000"]
2 changes: 1 addition & 1 deletion chassis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

from chassis.actions import execute_action, register_handler, register_handlers
from chassis.app import LifecycleHook, create_app
from chassis.chassis_app import LifecycleHook, create_app
from chassis.errors import (
AuthorizationError,
ChassisError,
Expand Down
177 changes: 177 additions & 0 deletions domains/revopsos/constellation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# --- L9_META ---
# l9_schema: 1
# layer: [domain]
# tags: [plangraph, constellation, revopsos]
# status: active
# --- /L9_META ---
# RevOpsOS constellation — 8-service revenue operations platform

constellation: revopsos
Comment on lines +1 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add YAML document start marker.

The YAML linter flags a missing document start marker. Adding --- improves YAML standards compliance and compatibility with multi-document parsers.

📝 Proposed fix
 # --- L9_META ---
 # l9_schema: 1
 # layer: [domain]
 # tags: [plangraph, constellation, revopsos]
 # status: active
 # --- /L9_META ---
+---
 # RevOpsOS constellation — 8-service revenue operations platform

 constellation: revopsos
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# --- L9_META ---
# l9_schema: 1
# layer: [domain]
# tags: [plangraph, constellation, revopsos]
# status: active
# --- /L9_META ---
# RevOpsOS constellation — 8-service revenue operations platform
constellation: revopsos
# --- L9_META ---
# l9_schema: 1
# layer: [domain]
# tags: [plangraph, constellation, revopsos]
# status: active
# --- /L9_META ---
---
# RevOpsOS constellation — 8-service revenue operations platform
constellation: revopsos
🧰 Tools
🪛 GitHub Check: YAML Validation

[warning] 9-9:
9:1 [document-start] missing document start "---"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@domains/revopsos/constellation.yaml` around lines 1 - 9, Add a YAML document
start marker by inserting `---` at the top of the file so the file begins with a
YAML document start before the existing L9_META block; specifically update the
file containing the L9_META comments and the `constellation: revopsos` entry so
parsers recognize a valid YAML document start.


services:
ENRICH:
status: built
description: Contact and company data enrichment from external sources
depends_on: []

GRAPH:
status: built
description: Relationship graph engine — maps accounts, contacts, and deals
depends_on: []

SCORE:
status: planned
description: ML-based lead and account scoring
depends_on: [ENRICH, GRAPH]

ROUTE:
status: planned
description: Intelligent lead and opportunity routing to reps/queues
depends_on: [SCORE, GRAPH]

FORECAST:
status: planned
description: Revenue forecast engine using pipeline + historical signals
depends_on: [SCORE, ROUTE]

SIGNAL:
status: planned
description: Real-time intent and behavioral signal ingestion
depends_on: [ENRICH]

HEALTH:
status: planned
description: Account health scoring and churn risk detection
depends_on: [SCORE, ENRICH]

HANDOFF:
status: planned
description: Sales-to-CS handoff automation and playbook execution
depends_on: [ROUTE, SCORE]

interfaces:
- name: enrich_inbound
service: ENRICH
direction: inbound
protocol: http
description: Accepts raw contact/company records for enrichment

- name: enrich_outbound
service: ENRICH
direction: outbound
protocol: http
description: Emits enriched records to downstream services

- name: graph_inbound
service: GRAPH
direction: inbound
protocol: bolt
description: Receives entity relationship data

- name: graph_query
service: GRAPH
direction: outbound
protocol: bolt
description: Serves graph queries to SCORE and ROUTE

- name: score_inbound
service: SCORE
direction: inbound
protocol: http
description: Accepts enriched + graph data for scoring

- name: score_outbound
service: SCORE
direction: outbound
protocol: http
description: Emits scored leads/accounts to ROUTE, FORECAST, HEALTH, HANDOFF

- name: route_inbound
service: ROUTE
direction: inbound
protocol: http
description: Accepts scored leads for routing decisions

- name: route_outbound
service: ROUTE
direction: outbound
protocol: http
description: Emits routing decisions to CRM and HANDOFF

- name: forecast_inbound
service: FORECAST
direction: inbound
protocol: http
description: Accepts pipeline + score data for forecasting

- name: signal_inbound
service: SIGNAL
direction: inbound
protocol: webhook
description: Receives real-time behavioral events

- name: signal_outbound
service: SIGNAL
direction: outbound
protocol: http
description: Forwards processed signals to ENRICH and SCORE

- name: health_inbound
service: HEALTH
direction: inbound
protocol: http
description: Accepts account data for health scoring

- name: handoff_inbound
service: HANDOFF
direction: inbound
protocol: http
description: Triggers handoff workflows from routing decisions

flows:
- from: ENRICH
to: SCORE
label: enriched_data

- from: GRAPH
to: SCORE
label: graph_context

- from: SCORE
to: ROUTE
label: scored_leads

- from: SCORE
to: FORECAST
label: score_signals

- from: SCORE
to: HEALTH
label: health_inputs

- from: SCORE
to: HANDOFF
label: handoff_triggers

- from: ROUTE
to: FORECAST
label: routing_outcomes

- from: ROUTE
to: HANDOFF
label: handoff_assignments

- from: SIGNAL
to: ENRICH
label: signal_enrichment
feedback: false

- from: HANDOFF
to: ROUTE
label: handoff_feedback
feedback: true

- from: FORECAST
to: SCORE
label: forecast_feedback
feedback: true
97 changes: 97 additions & 0 deletions engine/boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# --- L9_META ---
# l9_schema: 1
# layer: [api]
# tags: [chassis, lifecycle, boot]
# status: active
# --- /L9_META ---
"""engine/boot.py — CodegraphLifecycle: chassis LifecycleHook for l9-codegraph."""
from __future__ import annotations

import logging
from typing import Any

from chassis.chassis_app import LifecycleHook
from engine.settings import Settings

logger = logging.getLogger(__name__)
settings = Settings()


class CodegraphLifecycle(LifecycleHook):
"""Wires Neo4j connections for CodeGraph + PlanGraph engines."""

def __init__(self) -> None:
self._driver: Any = None

async def startup(self) -> None:
logger.info("CodegraphLifecycle.startup — Neo4j: %s", settings.neo4j_uri)
# Import here to avoid loading neo4j at module level (keeps tests fast)
from neo4j import AsyncGraphDatabase
self._driver = AsyncGraphDatabase.driver(
settings.neo4j_uri,
auth=(settings.neo4j_user, settings.neo4j_password),
)
logger.info("CodegraphLifecycle.startup complete")
Comment on lines +26 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Driver created at startup is unused.

The AsyncGraphDatabase.driver created here is never passed to handlers. Each handler (handle_search_codegraph, handle_build_codegraph) creates its own synchronous GraphDatabase.driver connection via EgoGraphRetriever and CodeGraphBuilder (see engine/codegraph/handler.py lines 42-46, 90-96).

Consider either:

  1. Passing self._driver to handlers for connection reuse
  2. Removing the startup driver if handlers manage their own connections

Current implementation creates unnecessary Neo4j connections.

Option A: Remove unused driver setup

If handlers will continue managing their own connections:

     async def startup(self) -> None:
         logger.info("CodegraphLifecycle.startup — Neo4j: %s", settings.neo4j_uri)
-        # Import here to avoid loading neo4j at module level (keeps tests fast)
-        from neo4j import AsyncGraphDatabase
-        self._driver = AsyncGraphDatabase.driver(
-            settings.neo4j_uri,
-            auth=(settings.neo4j_user, settings.neo4j_password),
-        )
+        # Handlers manage their own Neo4j connections
         logger.info("CodegraphLifecycle.startup complete")

     async def shutdown(self) -> None:
-        if self._driver:
-            await self._driver.close()
         logger.info("CodegraphLifecycle.shutdown complete")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/boot.py` around lines 26 - 34, The startup method currently creates
self._driver via AsyncGraphDatabase.driver but never uses it; either remove that
driver creation from CodegraphLifecycle.startup or change handlers to reuse it:
update startup to not call AsyncGraphDatabase.driver if you want handlers
(handle_search_codegraph, handle_build_codegraph) to manage their own
connections, or modify those handlers (and the classes EgoGraphRetriever and
CodeGraphBuilder) to accept and use the injected async driver (self._driver)
instead of creating new synchronous GraphDatabase.driver instances so
connections are reused.


async def shutdown(self) -> None:
if self._driver:
await self._driver.close()
logger.info("CodegraphLifecycle.shutdown complete")

async def execute(
self,
action: str,
payload: dict[str, Any],
tenant: str,
trace_id: str,
) -> dict[str, Any]:
logger.info("execute action=%s tenant=%s trace_id=%s", action, tenant, trace_id)
# Inject tenant + trace_id into payload so handlers can log them
enriched = {**payload, "tenant": tenant, "trace_id": trace_id}

try:
if action == "search_codegraph":
from engine.codegraph.handler import handle_search_codegraph
data = await handle_search_codegraph(enriched)
elif action == "build_codegraph":
from engine.codegraph.handler import handle_build_codegraph
data = await handle_build_codegraph(enriched)
elif action == "search_plangraph":
from engine.plangraph.handler import handle_search_plangraph
data = await handle_search_plangraph(enriched)
elif action == "build_order":
from engine.plangraph.handler import handle_build_order
data = await handle_build_order(enriched)
elif action == "check_drift":
from engine.plangraph.handler import handle_check_drift
data = await handle_check_drift(enriched)
elif action == "load_constellation":
from engine.plangraph.handler import handle_load_constellation
data = await handle_load_constellation(enriched)
elif action == "health":
data = {"status": "healthy", "service": "l9-codegraph"}
else:
return {
"status": "failed",
"action": action,
"tenant": tenant,
"data": {"error": f"Unknown action: '{action}'"},
"meta": {"trace_id": trace_id},
}
except Exception as exc:
logger.exception("Handler failed action=%s: %s", action, exc)
return {
"status": "failed",
"action": action,
"tenant": tenant,
"data": {"error": str(exc)},
"meta": {"trace_id": trace_id},
}

return {
"status": "ok",
"action": action,
"tenant": tenant,
"data": data,
"meta": {"trace_id": trace_id},
}
12 changes: 12 additions & 0 deletions engine/codegraph/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# --- L9_META ---
# l9_schema: 1
# layer: [engine]
# tags: [codegraph, repograph]
# status: active
# --- /L9_META ---
"""Code Graph Engine — RepoGraph-inspired structural code intelligence."""

from .builder import CodeGraphBuilder
from .retriever import EgoGraphRetriever

__all__ = ["CodeGraphBuilder", "EgoGraphRetriever"]
Loading
Loading