Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions codex-rs/core/src/project_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,9 @@ mod tests {
)
.unwrap_or_else(|_| cfg.codex_home.join("skills/pdf-processing/SKILL.md"));
let expected_path_str = expected_path.to_string_lossy().replace('\\', "/");
let usage_rules = "- Discovery: Available skills are listed in project docs and may also appear in a runtime \"## Skills\" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.";
let expected = format!(
"base doc\n\n## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- pdf-processing: extract from pdfs (file: {expected_path_str})"
"base doc\n\n## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- pdf-processing: extract from pdfs (file: {expected_path_str})\n{usage_rules}"
);
assert_eq!(res, expected);
}
Expand All @@ -535,8 +536,9 @@ mod tests {
dunce::canonicalize(cfg.codex_home.join("skills/linting/SKILL.md").as_path())
.unwrap_or_else(|_| cfg.codex_home.join("skills/linting/SKILL.md"));
let expected_path_str = expected_path.to_string_lossy().replace('\\', "/");
let usage_rules = "- Discovery: Available skills are listed in project docs and may also appear in a runtime \"## Skills\" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.\n- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.\n- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.\n- How to use a skill (progressive disclosure):\n 1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.\n 2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.\n 3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.\n 4) If `assets/` or templates exist, reuse them instead of recreating from scratch.\n- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.\n- Coordination and sequencing:\n - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.\n - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.\n- Context hygiene:\n - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.\n - Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.\n - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.\n- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.";
let expected = format!(
"## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- linting: run clippy (file: {expected_path_str})"
"## Skills\nThese skills are discovered at startup from ~/.codex/skills; each entry shows name, description, and file path so you can open the source for full instructions. Content is not inlined to keep context lean.\n- linting: run clippy (file: {expected_path_str})\n{usage_rules}"
);
assert_eq!(res, expected);
}
Expand Down
21 changes: 21 additions & 0 deletions codex-rs/core/src/skills/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,26 @@ pub fn render_skills_section(skills: &[SkillMetadata]) -> Option<String> {
));
}

lines.push(
r###"- Discovery: Available skills are listed in project docs and may also appear in a runtime "## Skills" section (name + description + file path). These are the sources of truth; skill bodies live on disk at the listed paths.
- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
- How to use a skill (progressive disclosure):
1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.
2) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.
3) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.
4) If `assets/` or templates exist, reuse them instead of recreating from scratch.
- Description as trigger: The YAML `description` in `SKILL.md` is the primary trigger signal; rely on it to decide applicability. If unsure, ask a brief clarification before proceeding.
- Coordination and sequencing:
- If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
- Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
- Context hygiene:
- Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
- Avoid deeply nested references; prefer one-hop files explicitly linked from `SKILL.md`.
- When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."###
.to_string(),
);

Some(lines.join("\n"))
}
17 changes: 16 additions & 1 deletion codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use codex_core::AuthManager;
use codex_core::ConversationManager;
use codex_core::config::Config;
use codex_core::config::edit::ConfigEditsBuilder;
#[cfg(target_os = "windows")]
use codex_core::features::Feature;
use codex_core::model_family::find_family_for_model;
use codex_core::openai_models::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
Expand All @@ -37,6 +36,7 @@ use codex_core::protocol::Op;
use codex_core::protocol::SessionSource;
use codex_core::protocol::TokenUsage;
use codex_core::skills::load_skills;
use codex_core::skills::model::SkillMetadata;
use codex_protocol::ConversationId;
use codex_protocol::openai_models::ModelUpgrade;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
Expand Down Expand Up @@ -231,6 +231,8 @@ pub(crate) struct App {

// One-shot suppression of the next world-writable scan after user confirmation.
skip_world_writable_scan_once: bool,

pub(crate) skills: Option<Vec<SkillMetadata>>,
}

impl App {
Expand Down Expand Up @@ -285,6 +287,12 @@ impl App {
}
}

let skills = if config.features.enabled(Feature::Skills) {
Some(skills_outcome.skills.clone())
} else {
None
};

let enhanced_keys_supported = tui.enhanced_keys_supported();

let mut chat_widget = match resume_selection {
Expand All @@ -298,6 +306,7 @@ impl App {
enhanced_keys_supported,
auth_manager: auth_manager.clone(),
feedback: feedback.clone(),
skills: skills.clone(),
is_first_run,
};
ChatWidget::new(init, conversation_manager.clone())
Expand All @@ -322,6 +331,7 @@ impl App {
enhanced_keys_supported,
auth_manager: auth_manager.clone(),
feedback: feedback.clone(),
skills: skills.clone(),
is_first_run,
};
ChatWidget::new_from_existing(
Expand Down Expand Up @@ -357,6 +367,7 @@ impl App {
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
skills,
};

// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
Expand Down Expand Up @@ -476,6 +487,7 @@ impl App {
enhanced_keys_supported: self.enhanced_keys_supported,
auth_manager: self.auth_manager.clone(),
feedback: self.feedback.clone(),
skills: self.skills.clone(),
is_first_run: false,
};
self.chat_widget = ChatWidget::new(init, self.server.clone());
Expand Down Expand Up @@ -523,6 +535,7 @@ impl App {
enhanced_keys_supported: self.enhanced_keys_supported,
auth_manager: self.auth_manager.clone(),
feedback: self.feedback.clone(),
skills: self.skills.clone(),
is_first_run: false,
};
self.chat_widget = ChatWidget::new_from_existing(
Expand Down Expand Up @@ -1147,6 +1160,7 @@ mod tests {
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
skills: None,
}
}

Expand Down Expand Up @@ -1184,6 +1198,7 @@ mod tests {
pending_update_action: None,
suppress_shutdown_complete: false,
skip_world_writable_scan_once: false,
skills: None,
},
rx,
op_rx,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/app_backtrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ impl App {
enhanced_keys_supported: self.enhanced_keys_supported,
auth_manager: self.auth_manager.clone(),
feedback: self.feedback.clone(),
skills: self.skills.clone(),
is_first_run: false,
};
self.chat_widget =
Expand Down
Loading
Loading