-
Notifications
You must be signed in to change notification settings - Fork 6.9k
Reimplement skills loading using SkillsManager + skills/list op. #7914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ba5a045 to
b1adbc0
Compare
| pub fn skills_for_cwd(&self, cwd: &Path) -> SkillLoadOutcome { | ||
| let cached = match self.cache_by_cwd.read() { | ||
| Ok(cache) => cache.get(cwd).cloned(), | ||
| Err(err) => err.into_inner().get(cwd).cloned(), | ||
| }; | ||
| if let Some(outcome) = cached { | ||
| return outcome; | ||
| } | ||
|
|
||
| let mut roots = vec![user_skills_root(&self.codex_home)]; | ||
| if let Some(repo_root) = repo_skills_root(cwd) { | ||
| roots.push(repo_root); | ||
| } | ||
| let outcome = load_skills_from_roots(roots); | ||
| match self.cache_by_cwd.write() { | ||
| Ok(mut cache) => { | ||
| cache.insert(cwd.to_path_buf(), outcome.clone()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary for SkillsManager to cache SkillLoadOutcome for the lifetime of the host process (keyed by cwd) rather than caching per session/conversation (e.g. load once during Codex::spawn)?
As it is, edits/additions to a skill won’t be reflected by skills/list or by starting a “new chat” inside the same long-lived backend, e.g.:
- VS Code “New chat” creates a new conversation but reuses the same backend process + SkillsManager cache for that cwd.
- TUI
/newstarts a new session in the same process, so it also reuses the cached skills for that cwd.
If skill hot-refreshing is planned as a direct follow-up, this may be fine, but otherwise I feel like users may expect “new chat” to reflect the latest skills.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback. This change is just the first step toward the dynamic loading you’re envisioning. skills/list can be called more frequently, with optional forced reloads.
ambrosino-oai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't speak to the rust code, but from an interface perspective for App/VSCE this LGTM
| pub name: String, | ||
| pub description: String, | ||
| pub path: PathBuf, | ||
| pub scope: SkillScope, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll want the icon fields too (cc @gverma-openai @edward-bayes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could extend the API later. This is just a minimum set to start with.
| #[ts(export_to = "v2/")] | ||
| pub struct SkillsListParams { | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub cwds: Option<Vec<PathBuf>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does UI need to associate individual skill with CWD it came from? Are we better served by asking for a single CWD at a time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, we return combinations of skills and errors. Hmm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this Optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about a case where you might want to query the default skills without tying them to a specific CWD, but yeah, maybe that’s not a valid use case.
| #[serde(rename_all = "camelCase")] | ||
| #[ts(export_to = "v2/")] | ||
| pub struct SkillsListResponse { | ||
| pub data: Vec<SkillsListEntry>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider adding pagination like other endpoints (models/list)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider having many directories open, do we want to wait for all of them to load skills before this method returns?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call! We can treat pagination/streaming as a follow-up optimization once we see more skills enabled in practice. For now, loading everything in one go keeps it simple, and clients that need finer-grained control can already split requests by cwd.
| Some(cwds) if !cwds.is_empty() => cwds, | ||
| _ => vec![self.config.cwd.clone()], | ||
| }; | ||
| let data = if self.config.features.enabled(Feature::Skills) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no strong feelings but consider encapsulating this check in skills_manager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m inclined to keep the Feature::Skills guard at the call site. SkillsManager doesn’t have access to config/session state, so pushing the check down would either require passing Config in (extra coupling) or just moving the same if behind another method without changing behavior.
codex-rs/app-server/README.md
Outdated
| - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. | ||
| - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). | ||
| - `model/list` — list available models (with reasoning effort options). | ||
| - `skills/list` — list skills for one or more `cwd` values; each skill includes a `scope` of `user` or `repo` (not thread-scoped). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(not thread-scoped). ? Prompt leftovers?
| #[cfg(any(test, feature = "test-support"))] | ||
| /// Construct with a dummy AuthManager containing the provided CodexAuth and codex home. | ||
| /// Used for integration tests: should not be used by ordinary business logic. | ||
| pub fn with_models_provider_and_home( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's have with_models_provider a call to this new method to have fewer copies of logic
| }); | ||
| sess.send_event(&turn_context, event).await; | ||
|
|
||
| let skills_outcome = if sess.enabled(Feature::Skills) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider encapsulating in skills_manager
|
|
||
| #[cfg(any(test, feature = "test-support"))] | ||
| /// Create an AuthManager with a specific CodexAuth and codex home, for testing only. | ||
| pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you care which home is set on auth?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes as the load skills load from home directory.
codex-rs/protocol/src/protocol.rs
Outdated
| ListSkills { | ||
| /// Optional working directories to scope repo skills discovery. | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| cwds: Option<Vec<PathBuf>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider required
|
@codex review again |
There was a problem hiding this 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
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let cached = match self.cache_by_cwd.read() { | ||
| Ok(cache) => cache.get(cwd).cloned(), | ||
| Err(err) => err.into_inner().get(cwd).cloned(), | ||
| }; | ||
| if let Some(outcome) = cached { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refresh cached skills when files change
SkillsManager returns the cached SkillLoadOutcome for a cwd without any invalidation, so once a cwd is cached, later edits/additions to SKILL.md files (or fixes to parse errors) will never be reflected in Op::ListSkills or skill injections until the process restarts. This is a regression from the prior per-session reload behavior and can leave users stuck with stale skills/errors in long‑lived sessions. Consider adding an explicit refresh path (e.g., on ListSkills) or invalidating based on file mtimes.
Useful? React with 👍 / 👎.
refactor the way we load and manage skills: