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.
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.
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.
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.
we'll want the icon fields too (cc @gverma-openai @edward-bayes)
There was a problem hiding this comment.
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.
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.
I see, we return combinations of skills and errors. Hmm.
There was a problem hiding this comment.
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.
consider adding pagination like other endpoints (models/list)
There was a problem hiding this comment.
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.
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.
no strong feelings but consider encapsulating this check in skills_manager
There was a problem hiding this comment.
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.
(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.
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.
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.
Do you care which home is set on auth?
There was a problem hiding this comment.
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>>, |
|
@codex review again |
There was a problem hiding this comment.
💡 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.
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: