-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
base: main
Are you sure you want to change the base?
Conversation
44f6068 to
ba5a045
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?
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
refactor the way we load and manage skills: