Skip to content
Draft
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
72 changes: 72 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,75 @@ Allowed prefixes: `LCORE-`, `RSPEED-`, `MGTM-`, `OLS-`, `RHIDP-`, `LEADS-`, `CWF
4. Follow existing code patterns in the module you're modifying
5. Write unit tests covering new functionality
6. Run format and verify before completion

## Lola Skills

These skills are installed by Lola and provide specialized capabilities.
When a task matches a skill's description, read the skill's SKILL.md file
to learn the detailed instructions and workflows.

**How to use skills:**
1. Check if your task matches any skill description below
2. Use `read_file` to read the skill's SKILL.md for detailed instructions
3. Follow the instructions in the SKILL.md file

<!-- lola:skills:start -->

### ixd-firefly-custom-skills

#### api-spec-validator

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines after each skill heading.

markdownlint reports MD022 because each #### heading is followed immediately by text. Insert one blank line after every skill heading.

Example
 #### api-spec-validator
+
 **When to use:** Validate API endpoint implementations...

Also applies to: 420-420, 424-424, 428-428, 432-432, 436-436, 440-440, 444-444

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 416-416: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 416, Update each skill heading in AGENTS.md, including the
headings at the referenced locations, to be followed by exactly one blank line
before its content. Preserve the heading text and all surrounding content while
resolving the markdownlint MD022 violations.

Source: Linters/SAST tools

**When to use:** Validate API endpoint implementations against OpenAPI or Swagger specifications for correctness and completeness. Use when checking API compliance, auditing spec drift, or verifying that route handlers match their documented contracts.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/api-spec-validator/SKILL.md` for detailed guidance.

#### code-reviewer
**When to use:** Perform structured code reviews with a comprehensive checklist covering correctness, security, performance, and maintainability. Use when reviewing a diff, pull request, merge request, or set of changed files before merge.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/code-reviewer/SKILL.md` for detailed guidance.

#### coverage-analyzer
**When to use:** Analyze test coverage reports to identify gaps in line, branch, and function coverage, then suggest targeted tests to close those gaps and reach 100% coverage. Use when coverage is below target, after adding new code, or when preparing for a release.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/coverage-analyzer/SKILL.md` for detailed guidance.

#### cypress-skill
**When to use:** Generates production-grade Cypress E2E and component tests in JavaScript or TypeScript, with support for local execution. Use when the user asks to write or debug Cypress tests, set up Cypress, work with cy commands, or mentions Cypress, "component test", "E2E test", "cy.", or common APIs such as cy.visit, cy.get, or cy.intercept.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/cypress-skill/SKILL.md` for detailed guidance.

#### evolution
**When to use:** Updates skill files based on user corrections during a session. Use when a user's guidance contradicts or extends a loaded skill's instructions, enabling skills to learn and improve from real-world usage.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/evolution/SKILL.md` for detailed guidance.

#### flaky-test-detector
**When to use:** Identify flaky tests by running test suites multiple times and analyzing inconsistent pass/fail results. Use when tests fail intermittently, CI builds are unreliable, or when diagnosing non-deterministic test behavior.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/flaky-test-detector/SKILL.md` for detailed guidance.

#### security-scanner
**When to use:** Scan source code for common security vulnerabilities including injection flaws, authentication issues, sensitive data exposure, XSS, and dependency CVEs. Use when performing security audits, reviewing code before deployment, or hardening an application.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/security-scanner/SKILL.md` for detailed guidance.

#### test-runner
**When to use:** Execute project test suites for unit testing with coverage and functional testing. Use when running tests, checking coverage, verifying test results, or executing a specific test file or method.
**Instructions:** Read `.lola/modules/ixd-firefly-custom-skills/skills/test-runner/SKILL.md` for detailed guidance.

<!-- lola:skills:end -->

<!-- lola:instructions:start -->
<!-- lola:module:ixd-firefly-custom-skills:start -->
---
name: ixd-firefly-custom-skills
description: Shared agent skills for harness-cli and OpenCode following the Agent Skills standard
---

# Harness Skills Module

This AI Context Module provides shared skills for use with [harness-cli](https://github.com/your-org/ixd-firefly-harness-engineering-cli) and [OpenCode](https://opencode.ai).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the placeholder repository URL.

The link contains your-org, so it does not identify the actual repository. Replace it with the canonical URL or remove the link.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 459, Update the harness-cli link in the AI Context Module
description to use the repository’s canonical URL, or remove the link if no
canonical URL is available; eliminate the placeholder “your-org” URL while
preserving the surrounding description.


All skills in this module follow the [Agent Skills](https://agentskills.io) open standard, making them compatible with any agent that supports the specification, including **OpenCode**, **Claude Code**, **Cursor**, **GitHub Copilot**, **Gemini CLI**, and [many others](https://agentskills.io/home).

## Available Skills

Skills include: `test-runner`, `coverage-analyzer`, `flaky-test-detector`, `code-reviewer`, `security-scanner`, `api-spec-validator`, `cypress-skill`, and `evolution` (enables skills to learn from user corrections during sessions). See [SKILLS_QUICKSTART.md](./SKILLS_QUICKSTART.md) for the complete list with descriptions.

## Installation

Install skills into your project using `harness-cli` or manually by copying skill directories into your project's `.opencode/skills/` directory. See [README.md](./README.md) for full installation and usage instructions.
<!-- lola:module:ixd-firefly-custom-skills:end -->
<!-- lola:instructions:end -->
4 changes: 1 addition & 3 deletions src/app/endpoints/rags.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ async def get_rag_endpoint_handler(
logger.info("Llama stack config: %s", llama_stack_configuration)

# Resolve user-facing rag_id to llama-stack vector_db_id
vector_db_id = _resolve_rag_id_to_vector_db_id(
rag_id, configuration.configuration.byok_rag
)
vector_db_id = _resolve_rag_id_to_vector_db_id(rag_id, configuration.byok_rag)

try:
# try to get Llama Stack client
Expand Down
7 changes: 5 additions & 2 deletions src/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,13 @@ def _enrich_library_config(self, input_config_path: str) -> str:
config = configuration.configuration

# Enrichment: BYOK RAG
enrich_byok_rag(ls_config, [b.model_dump() for b in config.byok_rag])
enrich_byok_rag(
ls_config,
[b.model_dump() for b in config.rag.byok.stores],
)

# Enrichment: Solr - enabled when "okp" appears in either inline or tool list
enrich_solr(ls_config, config.rag.model_dump(), config.okp.model_dump())
enrich_solr(ls_config, config.rag.model_dump(), config.rag.okp.model_dump())

# Enrichment: Azure Entra ID deferred auth
entra_id_config = (
Expand Down
35 changes: 28 additions & 7 deletions src/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
AuthenticationConfiguration,
AuthorizationConfiguration,
AzureEntraIdConfiguration,
ByokRag,
CompactionConfiguration,
Configuration,
ConversationHistoryConfiguration,
Expand Down Expand Up @@ -534,10 +535,10 @@ def approvals_configuration(self) -> ApprovalsConfiguration:

@property
def okp(self) -> "OkpConfiguration":
"""Return OKP configuration."""
"""Return OKP configuration from the unified rag section."""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.okp
return self._configuration.rag.okp
Comment on lines +538 to +541

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a Raises section to the okp docstring.

The property raises LogicError when configuration is not loaded. The coding guidelines require a Raises section. The new byok_rag property below already documents this correctly.

📝 Proposed change
-        """Return OKP configuration from the unified rag section."""
+        """Return OKP configuration from the unified rag section.
+
+        Returns:
+            OkpConfiguration: The OKP configuration from ``rag.okp``.
+
+        Raises:
+            LogicError: If the configuration has not been loaded.
+        """

As per coding guidelines: "Follow Google Python docstring conventions for all modules, classes, and functions, including relevant Parameters, Returns, Raises, and Attributes sections."

📝 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
"""Return OKP configuration from the unified rag section."""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.okp
return self._configuration.rag.okp
"""Return OKP configuration from the unified rag section.
Returns:
OkpConfiguration: The OKP configuration from ``rag.okp``.
Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.rag.okp
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/configuration.py` around lines 538 - 541, Add a Google-style Raises
section to the okp property docstring, documenting that it raises LogicError
when _configuration is not loaded. Leave the existing behavior and return
documentation unchanged.

Source: Coding guidelines


@property
def reranker(self) -> "RerankerConfiguration":
Expand All @@ -553,6 +554,22 @@ def skills(self) -> Optional[SkillsConfiguration]:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.skills

@property
def byok_rag(self) -> list[ByokRag]:
"""Return BYOK RAG stores from the unified rag configuration.

Backward-compatible property that returns ``rag.byok.stores``.

Returns:
list[ByokRag]: The list of BYOK RAG store configurations.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return self._configuration.rag.byok.stores

@property
def rag_id_mapping(self) -> dict[str, str]:
"""Return mapping from vector_db_id to rag_id from BYOK and OKP RAG config.
Expand All @@ -567,12 +584,15 @@ def rag_id_mapping(self) -> dict[str, str]:
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
byok_mapping = {
brag.vector_db_id: brag.rag_id for brag in self._configuration.byok_rag
brag.vector_db_id: brag.rag_id
for brag in self._configuration.rag.byok.stores
}

rag = self._configuration.rag
okp_id = constants.OKP_RAG_ID
okp_enabled = okp_id in (rag.inline or []) or okp_id in (rag.tool or [])
inline_sources = rag.retrieval.inline.sources
tool_sources = rag.retrieval.tool.sources
okp_enabled = okp_id in inline_sources or okp_id in tool_sources
okp_mapping = (
{constants.SOLR_DEFAULT_VECTOR_STORE_ID: okp_id} if okp_enabled else {}
)
Expand All @@ -593,22 +613,23 @@ def score_multiplier_mapping(self) -> dict[str, float]:
raise LogicError("logic error: configuration is not loaded")
return {
brag.vector_db_id: brag.score_multiplier
for brag in self._configuration.byok_rag
for brag in self._configuration.rag.byok.stores
}

@property
def inline_solr_enabled(self) -> bool:
"""Return whether OKP is included in the inline RAG list.

Returns:
bool: True if 'okp' appears in rag.inline, False otherwise.
bool: True if 'okp' appears in rag.retrieval.inline.sources,
False otherwise.

Raises:
LogicError: If the configuration has not been loaded.
"""
if self._configuration is None:
raise LogicError("logic error: configuration is not loaded")
return constants.OKP_RAG_ID in self._configuration.rag.inline
return constants.OKP_RAG_ID in self._configuration.rag.retrieval.inline.sources

def resolve_index_name(
self, vector_store_id: str, rag_id_mapping: Optional[dict[str, str]] = None
Expand Down
9 changes: 7 additions & 2 deletions src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,19 @@
USER_QUOTA_LIMITER: Final[str] = "user_limiter"
CLUSTER_QUOTA_LIMITER: Final[str] = "cluster_limiter"

# Hard cap on total RAG chunks delivered to the LLM across all sources
# Hard cap on total RAG chunks delivered to the LLM across all sources.
# Now user-configurable via rag.retrieval.inline.max_chunks; this constant
# serves as the default value.
INLINE_RAG_MAX_CHUNKS: Final[int] = 10

# RAG as a tool constants
DEFAULT_RAG_TOOL: Final[str] = "file_search"
# Now user-configurable via rag.retrieval.tool.max_chunks; this constant
# serves as the default value.
TOOL_RAG_MAX_CHUNKS: Final[int] = 10 # retrieved from RAG as a tool

# Inline RAG constants
# Inline RAG constants — now user-configurable via rag.byok.max_chunks and
# rag.okp.max_chunks respectively; these constants serve as default values.
BYOK_RAG_MAX_CHUNKS: Final[int] = 10 # retrieved from BYOK RAG
OKP_RAG_MAX_CHUNKS: Final[int] = 5 # retrieved from OKP RAG
# Score multiplier applied to BYOK chunks after cross-encoder reranking (Solr chunks unchanged)
Expand Down
91 changes: 85 additions & 6 deletions src/llama_stack_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,80 @@ def construct_vector_io_providers_section(
return output


def _extract_byok_rag_list(config: dict[str, Any]) -> list[dict[str, Any]]:
"""Extract BYOK RAG store list from either new or old config format.

Supports both the new ``rag.byok.stores`` path and the deprecated
top-level ``byok_rag`` key.

Parameters:
config: Raw lightspeed-stack.yaml dict.

Returns:
List of BYOK RAG store dicts (may be empty).
"""
# New format: rag.byok.stores
rag = config.get("rag")
if isinstance(rag, dict):
byok = rag.get("byok")
if isinstance(byok, dict):
stores = byok.get("stores")
if isinstance(stores, list) and stores:
return stores
# Old format: top-level byok_rag
return config.get("byok_rag") or []


def _extract_okp_dict(config: dict[str, Any]) -> dict[str, Any]:
"""Extract OKP configuration from either new or old config format.

Supports both the new ``rag.okp`` path and the deprecated top-level
``okp`` key.

Parameters:
config: Raw lightspeed-stack.yaml dict.

Returns:
OKP configuration dict (may be empty).
"""
# New format: rag.okp
rag = config.get("rag")
if isinstance(rag, dict):
okp = rag.get("okp")
if isinstance(okp, dict) and okp:
return okp
# Old format: top-level okp
return config.get("okp") or {}


def _extract_rag_source_ids(
rag_config: dict[str, Any],
) -> tuple[list[str], list[str]]:
"""Extract inline and tool RAG source IDs from either config format.

Supports both the new ``retrieval.inline.sources`` / ``retrieval.tool.sources``
path and the deprecated ``inline`` / ``tool`` keys.

Parameters:
rag_config: RAG configuration dict.

Returns:
Tuple of (inline_ids, tool_ids).
"""
retrieval = rag_config.get("retrieval")
if isinstance(retrieval, dict):
inline_cfg = retrieval.get("inline") or {}
tool_cfg = retrieval.get("tool") or {}
inline_ids = (
inline_cfg.get("sources") or [] if isinstance(inline_cfg, dict) else []
)
tool_ids = tool_cfg.get("sources") or [] if isinstance(tool_cfg, dict) else []
else:
inline_ids = rag_config.get("inline") or []
tool_ids = rag_config.get("tool") or []
return inline_ids, tool_ids
Comment on lines +518 to +543

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the deprecated rag.inline / rag.tool migration in the Configuration models.
set -euo pipefail

fd -t f 'config.py' src/models --exec rg -n -C15 'inline|tool' -g '*.py' --max-count 200 \
  | rg -n -C0 'deprecat|migrate|model_validator|warnings.warn' || true

fd -t f 'config.py' src/models --exec ast-grep run --lang python \
  --pattern 'def $NAME(self) -> Self:
    $$$'

Repository: lightspeed-core/lightspeed-stack

Length of output: 50388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^src/llama_stack_configuration\.py$|src/models/config\.py$|src/.*)' | sed -n '1,120p'

echo "== target function locations =="
rg -n "_extract_rag_source_ids|generate_configuration|synthesize_configuration|migrate_deprecated_rag_fields|generate.*yaml|run\.yaml" src -g '*.py'

echo "== target function =="
sed -n '480,570p' src/llama_stack_configuration.py

echo "== nearby references =="
sed -n '1080,1180p' src/llama_stack_configuration.py
sed -n '1180,1260p' src/llama_stack_configuration.py
sed -n '1320,1400p' src/llama_stack_configuration.py

Repository: lightspeed-core/lightspeed-stack

Length of output: 22212


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target function locations =="
rg -n "_extract_rag_source_ids|generate_configuration|synthesize_configuration|migrate_deprecated_rag_fields|generate.*yaml|run\.yaml" src -g '*.py'

echo "== target function =="
sed -n '480,570p' src/llama_stack_configuration.py

echo "== helpers around run.yaml generation/use =="
sed -n '1080,1180p' src/llama_stack_configuration.py
sed -n '1180,1260p' src/llama_stack_configuration.py
sed -n '1320,1400p' src/llama_stack_configuration.py

echo "== model migration snippet =="
sed -n '2800,2835p' src/models/config.py
sed -n '3255,3295p' src/models/config.py

Repository: lightspeed-core/lightspeed-stack

Length of output: 21521


Merge both RAG source-id formats in _extract_rag_source_ids.

When raw lightspeed-stack.yaml contains a retrieval dict and also deprecated flat rag.inline / rag.tool lists, the current isinstance branch discards the flat lists. This means Solr can be emitted without the OKP inline IDs included. Merge the nested sources values with the deprecated flat lists before returning inline/tool IDs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llama_stack_configuration.py` around lines 518 - 543, Update
_extract_rag_source_ids to merge nested retrieval.inline.sources and
retrieval.tool.sources with the deprecated top-level inline and tool lists when
both formats are present. Preserve support for either format, combine each
corresponding pair before returning, and avoid discarding flat IDs when
retrieval is a dictionary.



def enrich_byok_rag(ls_config: dict[str, Any], byok_rag: list[dict[str, Any]]) -> None:
"""Enrich Llama Stack config with BYOK RAG settings.

Expand Down Expand Up @@ -783,8 +857,7 @@ def enrich_solr( # pylint: disable=too-many-locals
- chunk_filter_query (str): Solr filter query for chunk retrieval
- rhokp_url (str): OKP/Solr base URL (e.g. from ${env.RH_SERVER_OKP})
"""
inline_ids = rag_config.get("inline") or []
tool_ids = rag_config.get("tool") or []
inline_ids, tool_ids = _extract_rag_source_ids(rag_config)
okp_enabled = constants.OKP_RAG_ID in inline_ids or constants.OKP_RAG_ID in tool_ids

if not okp_enabled:
Expand Down Expand Up @@ -1157,9 +1230,12 @@ def synthesize_configuration(
# 4. Existing enrichment — same calls as legacy generate_configuration so
# unified output matches legacy output for equivalent inputs (R7).
enrich_azure_entra_id_inference(ls_config, lcs_config.get("azure_entra_id"))
enrich_byok_rag(ls_config, lcs_config.get("byok_rag", []))
byok_rag_list = _extract_byok_rag_list(lcs_config)
enrich_byok_rag(ls_config, byok_rag_list)
enrich_vector_store(ls_config, lcs_config.get("vector_store"))
enrich_solr(ls_config, lcs_config.get("rag", {}), lcs_config.get("okp", {}))
rag_dict = lcs_config.get("rag", {})
okp_dict = _extract_okp_dict(lcs_config)
enrich_solr(ls_config, rag_dict, okp_dict)

# 5. High-level inference providers (Decision S5 — a root-level section).
inference = lcs_config.get("inference") or {}
Expand Down Expand Up @@ -1328,10 +1404,13 @@ def generate_configuration(
enrich_azure_entra_id_inference(ls_config, config.get("azure_entra_id"))

# Enrichment: BYOK RAG
enrich_byok_rag(ls_config, config.get("byok_rag", []))
byok_rag_list = _extract_byok_rag_list(config)
enrich_byok_rag(ls_config, byok_rag_list)

# Enrichment: Solr - enabled when "okp" appears in either inline or tool list
enrich_solr(ls_config, config.get("rag", {}), config.get("okp", {}))
rag_dict = config.get("rag", {})
okp_dict = _extract_okp_dict(config)
enrich_solr(ls_config, rag_dict, okp_dict)

dedupe_providers_vector_io(ls_config)

Expand Down
Loading
Loading