Skip to content

[Bug] Turn-start skill retrieval bypasses getSkill() entirely — recordUse/recordTrial (and candidate graduation) never fire for the primary skill-consumption path #2366

Description

@chiefmojo

Pre-submission checklist

  • I have searched existing issues and this hasn't been mentioned before
  • I have read the project documentation and confirmed this issue doesn't already exist
  • This issue is specific to MemOS and not a general software issue

Bug Description

Summary

Skill graduation (candidate → active) requires trialsAttempted >= cfg.candidateTrials (core/skill/lifecycle.ts), and trialsAttempted only increments via applyTrial, which only fires from recordSkillTrial (core/pipeline/memory-core.ts), which only runs when getSkill() is called with recordTrial: true. But the primary path by which an agent actually consumes a skill — turn-start retrieval (core/retrieval/tier1-skill.tsSkillCandidate) — reads repos.skills directly and injects the full invocationGuide into context without ever calling getSkill(). recordUse has the same single gate.

Net effect: the routine, load-bearing path that surfaces and uses skills generates zero lifecycle signal. The only path that generates signal is a separate, optional tool call (skill.get via e.g. memos_skill_get) that an agent has little reason to make once turn-start injection has already given it the full invocation guide. Skill candidates starve for trials not because any adapter is broken, but because MemOS's own retrieval architecture bypasses its own instrumentation point.

Evidence

Ruled out the two obvious "just misconfigured" explanations before concluding this is structural:

  • The Hermes adapter's tool wiring is correct. adapters/hermes/memos_provider/__init__.py registers memos_skill_get in get_tool_schemas() and calls skill.get with recordTrial: True in handle_tool_call(). Not a wiring bug.
  • The tool isn't disabled. _disabled_retrieval_tools() is gated by MEMOS_RETRIEVAL_TOOLS_DISABLED; verified unset (commented out, not exported) on all three of our production hosts.
  • SkillCandidate (core/retrieval/types.ts) carries the full invocationGuide: string, not a pointer/id requiring a follow-up call — confirmed the field is populated end-to-end from repos.skills through to the injected context.
  • recordUse and recordTrial have exactly one call site between them: getSkill() in core/pipeline/memory-core.ts. Grepped the full file; no other caller of either.

Fleet impact (three production hosts, deployed commit)

906 of 922 candidate skills (98.3%) have never had a single trial recorded, over roughly three months of active operation with regular turn-start skill retrieval happening continuously. Skills are being surfaced and used; almost none of that generates a trial.

Suggested Fix

A couple of directions, would appreciate maintainer input on which fits the intended design:

  1. Have turn-start skill injection call something equivalent to getSkill(..., { recordUse: true }) (or a lighter dedicated counter) when a SkillCandidate is actually surfaced into context — at minimum tracking exposure, ideally feeding into trial counting too.
  2. Alternatively, if trialsAttempted is meant to track deliberate invocation specifically (as distinct from passive context exposure), that's a reasonable design choice — but it means candidateTrials as currently tuned assumes a usage pattern (explicit skill lookup calls) that doesn't match how skills are actually consumed via turn-start retrieval, and the gate should be reconsidered against that reality.

Happy to contribute data or a PR once maintainers weigh in on intended design.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:coreMOS 编排层 / 框架底座 / 跨模块问题status:needs-product-reviewRequires product owner confirmation before deciding public repo, transfer, or internal backlog.types:bugSomething isn't working | 功能异常

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions