Skip to content

[Feature] Let an embedding application inject an LLM provider #243

Description

@vcolombo

Problem

The best time to scan a skill is the moment an agent is deciding whether to
install it — which increasingly means SkillSpector runs embedded inside a
host application
(an agent framework, an IDE runtime, a plugin sandbox) that
already has a configured, credentialed LLM client in-process.

Today there is no way to hand that client to SkillSpector. Provider selection
is a closed env-var dispatch (SKILLSPECTOR_PROVIDER
_select_active_provider()), so an embedder's only options are:

  • exporting API keys into the process environment (often impossible — hosts
    like agent-plugin runtimes deliberately never expose raw keys, only a
    governed in-process completion API with audit/trust controls), or
  • shelling out via a CLI-style provider (feat: subprocess provider, SQLite LLM cache, meta-analyzer batching, and scan UX improvements #234's subprocess provider serves
    interactive sessions well, but an embedded host's LLM handle is an object,
    not a CLI — and routing scan prompts, which embed untrusted skill
    content
    , through a tool-enabled agent CLI instead of a bare completion API
    is a prompt-injection hazard), or
  • monkeypatching _select_active_provider from the embedder (works — I'm
    doing it in a Hermes Agent plugin today — but nobody should have to patch a
    security scanner's internals).

The demand pattern already exists: #173/#174/#175 ask for more first-party
providers, #234 adds session-CLI support, #33 asks about programmatic/agent
embedding. This proposal is the missing third leg: bring-your-own provider
object
.

Proposal

A small injection surface — no new providers in-tree, ~30 lines:

# skillspector.providers
_INJECTED: ContextVar[LLMProvider | None] = ContextVar("injected_provider", default=None)

def use_provider(provider: LLMProvider) -> Token: ...   # bind for current context
def reset_provider(token: Token) -> None: ...

def _select_active_provider() -> LLMProvider:
    injected = _INJECTED.get()
    if injected is not None:
        return injected
    ...  # existing env dispatch unchanged

What I have working

I maintain a Hermes Agent plugin (hermes-plugin-skillspector) that embeds SkillSpector and runs the
semantic pass on the host agent's own model via its governed plugin-LLM API
(no keys ever reach the plugin). It works today via the monkeypatch route and
has been validated end-to-end on a live deployment; with this hook the patch
disappears entirely. Happy to send a PR with the hook + tests if there's
interest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions