Skip to content

Conversation

@ThomasK33
Copy link
Member

Adds v1 Agent Skills support:

  • Discover skills from /.mux/skills and ~/.mux/skills (project overrides global)
  • Inject an index into the system prompt with tool-based loading instructions
  • Add tools:
    • agent_skill_read (load + validate SKILL.md)
    • agent_skill_read_file (read referenced files within a skill dir, with traversal + size limits)

Includes Zod schemas/types, defensive parsing (unknown frontmatter keys ignored), and unit tests.


📋 Implementation Plan

🤖 Agent Skills integration plan (Mux)

Goals (v1)

  • Discover Agent Skills on disk from two roots:
    • Project-local: <projectRoot>/.mux/skills/<name>/SKILL.md
    • Global: ~/.mux/skills/<name>/SKILL.md
  • Resolve name collisions deterministically: project-local overrides global.
  • Inject a compact <agent-skills> index (name + description + how to load) into the system prompt.
  • Add dedicated tools:
    • agent_skill_read — read + validate a skill’s SKILL.md (frontmatter + body)
    • agent_skill_read_file — read a file within a skill directory (for referenced files)
  • Strong typing + validation via Zod; malformed skills must be skipped during discovery (never brick chat).

Explicit non-goals (v1)

  • No /skill command or other explicit “activate skill” UX.
  • No enforcement of allowed-tools.
  • No loosening of the existing file_read sandbox.

Spec recap (what we must support)

From https://agentskills.io/specification:

  • A skill is a directory containing at minimum SKILL.md.
  • SKILL.md contains:
    • YAML frontmatter:
      • required: name, description
      • optional: license, compatibility, metadata
      • allowed-tools exists in the spec but is ignored by mux (it must not break parsing).
    • markdown body: free-form instructions.

Repo integration points (confirmed)

  • System prompt is built in src/node/services/systemMessage.ts via buildSystemMessage(...).
    • It builds PRELUDE + <environment> + optional <mcp> first.
    • Best injection point is after the MCP context (right after the existing buildMCPContext call) and before the early variant === "agent" return, so both variants get the skill index.
  • Tools are defined in src/common/utils/tools/toolDefinitions.ts (Zod schemas) and registered in src/common/utils/tools/tools.ts (getToolsForModel).
  • Existing file_read is restricted to workspace CWD (with a plan-file exception), so skills need dedicated tools.

Implementation plan (tool-based, recommended) — net +~320–520 LoC (product code)

1) Add shared Agent Skill domain schemas/types (Zod-first)

Create:

  • src/common/orpc/schemas/agentSkill.ts
  • src/common/types/agentSkill.ts

Key points:

  • SkillNameSchema:
    • min(1), max(64)
    • regex: ^[a-z0-9]+(?:-[a-z0-9]+)*$
  • AgentSkillFrontmatterSchema:
    • { name, description, license?, compatibility?, metadata? }
    • Do not call .strict().
      • Leave Zod’s default “strip unknown keys” behavior so allowed-tools (and other unknown keys) are tolerated and ignored.
  • AgentSkillDescriptorSchema (system prompt index): { name, description, scope: "project" | "global" }
  • AgentSkillPackageSchema (tool output): { scope, directoryName, frontmatter, body }
    • refine(directoryName === frontmatter.name)
Draft schema sketch (no allowed-tools, tolerant parsing)
export const SkillNameSchema = z
  .string()
  .min(1)
  .max(64)
  .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);

export const AgentSkillFrontmatterSchema = z.object({
  name: SkillNameSchema,
  description: z.string().min(1).max(1024),
  license: z.string().optional(),
  compatibility: z.string().min(1).max(500).optional(),
  metadata: z.record(z.string(), z.string()).optional(),
}); // <- default: unknown keys stripped

2) Parse SKILL.md safely

Add a pure helper (easy to unit test), e.g.:

  • src/node/services/agentSkills/parseSkillMarkdown.ts

Behavior:

  • Enforce max file size using MAX_FILE_SIZE from src/node/services/tools/fileCommon.ts (1MB).
  • Require YAML frontmatter at the top: ---\n...\n---\n.
  • Parse YAML via a new dependency (yaml is the simplest fit).
  • Validate required fields with Zod.
  • Return { frontmatter, body }.

3) Runtime-aware discovery service

Add:

  • src/node/services/agentSkills/agentSkillsService.ts

Responsibilities:

  • Discover skill descriptors for prompt injection.
  • Resolve + read a specific skill by name for tools.

Roots (in this order for precedence):

  1. Project: ${projectPath}/.mux/skills
  2. Global: ~/.mux/skills

Notes:

  • Use ~/.mux (no -dev suffix), mirroring the plan storage behavior.
  • Discovery + reads should happen via the active Runtime (local/worktree/ssh).

Directory listing (since Runtime has no readdir):

  • runtime.stat(root) to check existence.
  • await runtime.resolvePath(root) before invoking shell commands (bash won’t expand ~ inside quotes).
  • Use execBuffered(runtime, ...) from src/node/utils/runtime/helpers.ts to run ls -1 and parse stdout.
    • Skill directory names are validated via SkillNameSchema.safeParse (so parsing ls output is safe).

Defensive behavior:

  • If a root doesn’t exist: return empty.
  • If a skill is malformed (missing SKILL.md, invalid YAML, name mismatch): log + skip for discovery.

Optional cache:

  • In-memory cache keyed by { projectPath, runtimeType }.
  • Invalidate based on root dir stat.modifiedTime (or disable cache if too complex initially).

4) System prompt injection (<agent-skills>)

In buildSystemMessage (src/node/services/systemMessage.ts):

  • After the MCP block is appended, discover descriptors and append:
<agent-skills>
Available agent skills (call tools to load):
- pdf-processing: Extract text and tables from PDFs. (scope: global)
- foo: ...

To load a skill:
- agent_skill_read({ name: "<skill-name>" })

To read referenced files inside a skill directory:
- agent_skill_read_file({ name: "<skill-name>", filePath: "references/whatever.txt" })
</agent-skills>

Rules:

  • Keep it compact: no absolute filesystem paths in the prompt.
  • Stable sort by name.
  • Cap at 50 skills; if truncated, mention “(+N more)”.

5) Add tool definitions + wiring

In src/common/utils/tools/toolDefinitions.ts:

  • Add agent_skill_read + agent_skill_read_file schemas.
  • Prefer success/error union results (pattern used by file_read).

In src/common/utils/tools/tools.ts:

  • Register both as runtime tools in getToolsForModel.

6) Implement tool handlers

Add:

  • src/node/services/tools/agent_skill_read.ts
  • src/node/services/tools/agent_skill_read_file.ts

Handler behavior:

  • agent_skill_read({ name }):

    • Resolve the skill using the discovery service.
    • Return { success: true, skill: AgentSkillPackage } or { success: false, error }.
  • agent_skill_read_file({ name, filePath, offset?, limit? }):

    • Resolve the skill dir.
    • Prevent traversal:
      • reject absolute paths and paths beginning with ~
      • use runtime.normalizePath(filePath, skillDir) and ensure it is within skillDir
    • Reuse fileCommon.validateFileSize + the same output limits as file_read.

7) Tests (prefer pure unit tests)

  • parseSkillMarkdown.test.ts:

    • parses valid frontmatter + body
    • rejects missing/invalid frontmatter
    • rejects name mismatch
    • tolerates unknown keys (e.g., allowed-tools) without failing
  • Discovery/precedence tests:

    • Extract merging/precedence into a pure function and unit test it (no runtime needed).
    • If practical, add a LocalRuntime-backed test that creates temp dirs and exercises the ls-based discovery.

Alternative (not shipping in v1): relax file_read sandbox

If we ever want “agent reads SKILL.md directly via file_read”, we’d need to extend the read sandbox to allow ~/.mux/skills and <projectRoot>/.mux/skills.

Est. net LoC: +~120–220 (product code)

Tradeoff: expands security surface area and still leaves unclear UX for referenced files.


Validation checklist

  • <agent-skills> block appears when at least one valid skill exists.
  • Project overrides global deterministically.
  • Invalid skills are skipped during discovery (chat continues normally).
  • Tool calls return typed { success:false, error } for missing/invalid skills.
  • agent_skill_read_file blocks ../ traversal and cannot read outside the skill directory.

Generated with mux • Model: openai:gpt-5.2 • Thinking: xhigh

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ThomasK33
Copy link
Member Author

Re: SSH runtime concern — in mux, WorkspaceMetadata.projectPath is the local project path even when runtimeConfig.type === "ssh" (it’s used as the source-of-truth for things like checking for a local .mux/init before syncing/running it remotely).\n\nAgent Skills are intentionally discovered from the same local roots as other user-controlled config:\n- <projectRoot>/.mux/skills (local project)\n- ~/.mux/skills (local home)\n\nThe agent only gets access to what you place inside a skill directory, and agent_skill_read_file enforces no absolute paths / no ".." traversal / no symlink escape + size/output limits.\n\nIf we decide we want skills to live on the SSH host instead, we can switch discovery+reads to go through the runtime (and/or look under the remote workspace), but that would be a different behavior than the current spec/local-config approach.

@ThomasK33
Copy link
Member Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ThomasK33
Copy link
Member Author

ThomasK33 commented Dec 22, 2025

@codex review

Implemented runtime-aware Agent Skills loading so SSH workspaces can discover/read skills via the active runtime (system prompt index + tools).

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Change-Id: Ic6403e04df7db28a075d2b4084fb9f3f330e9425
Signed-off-by: Thomas Kosiewski <[email protected]>
Change-Id: I94e8cfc58b4220045018e6f2d751560ffc4b39b9
Signed-off-by: Thomas Kosiewski <[email protected]>
Change-Id: I78abef7d37ac2e0a06a03dfad8e577a5becf2437
Signed-off-by: Thomas Kosiewski <[email protected]>
Change-Id: Id5fbd1107aa2b323e7ce065f0bf0b780dfc0b19a
Signed-off-by: Thomas Kosiewski <[email protected]>
Change-Id: Iaa402bb87f8a76413e38f6e4ab94b1c3b6d3eb11
Signed-off-by: Thomas Kosiewski <[email protected]>
Change-Id: Ie701433b134eac7cdf13730c7467bdcd72c3eb62
Signed-off-by: Thomas Kosiewski <[email protected]>
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