Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

refactor(skills): extract SkillRuntime abstraction from Agent#1074

Open
lyingbug wants to merge 2 commits intoTencent:mainTencent/WeKnora:mainfrom
lyingbug:feat/skill-runtime-decouplinglyingbug/WeKnora:feat/skill-runtime-decouplingCopy head branch name to clipboard
Open

refactor(skills): extract SkillRuntime abstraction from Agent#1074
lyingbug wants to merge 2 commits intoTencent:mainTencent/WeKnora:mainfrom
lyingbug:feat/skill-runtime-decouplinglyingbug/WeKnora:feat/skill-runtime-decouplingCopy head branch name to clipboard

Conversation

@lyingbug
Copy link
Copy Markdown
Collaborator

Summary

Decouple Skill capabilities from AgentEngine so they can be consumed by HTTP handlers, workflows, and any future module via a stable interface. The current design tightly couples *skills.Manager to the agent engine and rebuilds it on every agent request, blocking reuse and making the layer hard to evolve (no DB persistence, no RBAC decoration, no mocking, etc.).

This PR introduces a clean three-layer architecture without changing any external contract (HTTP response shape, env vars, AgentConfig fields are all preserved).

Architecture

Agent / Handler / Workflow  →  SkillRuntime (interface, DI singleton)
                                       │
                                       ▼
                              fileSystemRuntime (default impl)
                                       │
                                       ▼
                              SkillRepository (interface)
                                       │
                                       ▼
                              fsRepository (filesystem)

Key changes

  • New abstractions

    • internal/agent/skills/runtime.goSkillRuntime interface + ExecuteRequest / SkillInfo
    • internal/agent/skills/repository.goSkillRepository interface + fsRepository (path-traversal protection preserved)
    • internal/agent/skills/runtime_fs.gofileSystemRuntime consolidating the previous Manager and Loader, with explicit concurrency safety and idempotent Initialize
    • internal/agent/skills/factory.go — centralised sandbox construction and env-var parsing (NewRuntime / NewRuntimeFromEnv / FilterMetadata)
  • Consumers switched to the interface

    • tools/skill_read.go, tools/skill_execute.go depend on SkillRuntime (mockable)
    • agent/engine.go field renamed skillsManager → skillRuntime; per-request AllowedSkills filtering applied via FilterMetadata since the runtime is a global singleton
    • handler/skill_handler.go no longer reads env vars; skills_available comes from runtime.SandboxAvailable()
  • DI

    • container.go registers skills.NewRuntimeFromEnv as a singleton (single filesystem scan shared across requests, vs. per-request reconstruction before)
    • agent_service.NewAgentService now takes skills.SkillRuntime as a dependency; the 68-line initializeSkillsManager helper is removed
  • Deleted

    • internal/agent/skills/manager.go
    • internal/agent/skills/loader.go
    • internal/application/service/skill_service.go
    • internal/types/interfaces/skill.go

Backwards compatibility

  • GET /api/v1/skills response format unchanged (including skills_available)
  • AgentConfig.{SkillsEnabled, SkillDirs, AllowedSkills} JSON fields unchanged
  • Environment variables WEKNORA_SKILLS_DIR, WEKNORA_SANDBOX_* unchanged
  • Filesystem layout (skills/preloaded/<name>/SKILL.md) unchanged

Behaviour difference (intended improvement)

  • Skill discovery is now performed once at startup (DI singleton) instead of on every agent request. This is a performance win but means reload requires either restarting the process or calling runtime.Reload() (no HTTP endpoint added in this PR — can come later).

Future extension points unlocked

With this refactor, the following can each land as a small isolated PR:

  • DB / OSS / marketplace SkillRepository implementation
  • Retrieval-based Skill prompt injection (avoid token bloat with many skills)
  • RBAC / audit-logging via Runtime decorator
  • Parallel script execution (ExecuteBatch)
  • fsnotify hot-reload

Test plan

  • go build ./... passes
  • go vet ./... passes
  • go test ./internal/agent/... passes (including new tests covering Repository.Discover, GetByName, ReadFile path-traversal rejection, Runtime.Initialize, allow-list filtering, disabled state, FilterMetadata)
  • grep -r "skills.Manager\|NewSkillService\|interfaces.SkillService" internal/ returns empty
  • Manual smoke: start server with WEKNORA_SANDBOX_MODE=docker + skills enabled in agent config; verify read_skill and execute_skill_script tools work end-to-end against a preloaded skill
  • Manual smoke: GET /api/v1/skills returns the same JSON shape (data + skills_available) as before

Decouple Skill capabilities from AgentEngine so they can be consumed by
HTTP handlers, workflows and any future module via a stable interface.

Changes:
- Introduce skills.SkillRuntime interface (runtime.go) plus
  SkillRepository abstraction (repository.go) so the storage layer
  (currently filesystem only) can be swapped for DB / OSS / marketplace
  implementations later without touching consumers.
- Add fileSystemRuntime (runtime_fs.go) as the default implementation,
  consolidating the previous Manager and Loader logic with explicit
  concurrency safety and an idempotent Initialize.
- Centralise sandbox construction and env-var parsing in factory.go
  (NewRuntime / NewRuntimeFromEnv), removing duplicated bootstrap code
  from agent_service.
- Register SkillRuntime as a singleton in the DI container so a single
  filesystem scan is shared across all agent requests; per-request
  AllowedSkills filtering is applied at the engine layer via
  FilterMetadata.
- Tools (read_skill, execute_skill_script), AgentEngine and
  SkillHandler now depend on the SkillRuntime interface instead of the
  concrete *skills.Manager type, enabling easy mocking and future
  decoration (RBAC, audit logging, batch execution).
- Delete obsolete files: skills/manager.go, skills/loader.go,
  application/service/skill_service.go, types/interfaces/skill.go.
- Update tests to cover the new Repository / Runtime API including
  path-traversal protection, allow-list enforcement and disabled state.

HTTP contract (GET /api/v1/skills response shape and skills_available
field), AgentConfig fields and WEKNORA_* environment variables are all
preserved — no operator-visible changes.
Follow-ups from the review of the initial extraction:

- Remove AgentConfig.SkillDirs. The field was silently ignored after the
  runtime became a DI singleton, which hid a behaviour change behind an
  unchanged JSON surface. Drop it plus the two now-dead assignments in
  session_agent_qa.go. Agents now rely on the process-wide skill
  directory resolved from WEKNORA_SKILLS_DIR at startup.

- Rebuild fsRepository cache from scratch inside Discover so Reload
  correctly drops skills that have disappeared from the filesystem.

- Simplify NewRuntime / NewRuntimeFromEnv to return a single
  SkillRuntime value. The error slot never produced a non-nil error —
  sandbox construction failures already fall back to NewDisabledManager
  internally.

- Remove the unused MustBeReady helper.

- Harden path-traversal protection in fsRepository.ReadFile by
  comparing against absSkill + PathSeparator, preventing a sibling
  directory whose name starts with the target skill's name from being
  treated as nested inside it. Covered by a new regression test.

- Switch fileSystemRuntime.Initialize to sync.Once so concurrent
  first-time callers no longer trigger duplicate filesystem scans.
  Reload bypasses the once guard deliberately — it is the retry path.

- Fix struct field alignment in engine.go and wrap newFileSystemRuntime
  signature below the 120-char line limit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.