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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions