Before submitting
Area
apps/web
Problem or use case
My projects live in two trees that mean different things:
~/Developer/Personal/...
~/Developer/Work/...
Everything under Work should start on my work provider account and its approved model. Everything under Personal should start on my personal one. The split is a property of where the project lives, not of any individual project.
Today the default provider and model is a per-project field. defaultModelSelection already exists on the project record (packages/contracts/src/orchestration.ts:225, :424, :1086), project.meta.update accepts it (:629, :639), and draft/thread creation already consumes it. So the mechanism works — but it has to be set once per project, by hand, and again for every new project I add.
That does not scale past a handful of repos, and the failure mode is quiet: a new project under Work inherits nothing, so the first thread starts on whichever selection was last used, which may be the personal account. The mistake is usually only obvious after the turn has already run.
Proposed solution
Let a parent directory carry a default ModelSelection that projects underneath inherit.
- In sidebar/project settings, allow setting a default provider + model on a directory scope (e.g.
~/Developer/Work), not just on a single project.
- Resolve a project's effective default by walking its
workspaceRoot up to the nearest ancestor that has a default configured; the longest matching path prefix wins, so ~/Developer/Work/client-a can still override ~/Developer/Work.
- An explicit per-project
defaultModelSelection continues to win over any inherited one.
- New threads and drafts consume the resolved value through the path that already exists — no change to thread creation.
Path prefix matching already has a home: normalizeProjectPathForComparison and the ancestor/prefix logic in packages/client-runtime/src/state/projectGrouping.ts do this kind of comparison for the sidebar's directory grouping today.
Why this matters
It turns "keep work and personal accounts separate" from a per-project chore that silently fails on every new repo into a rule I state once. That matters for cost attribution, for subscription and rate limits landing on the right account, and for organizational boundaries where running a work repo through a personal account is a real problem rather than an inconvenience.
It is also the natural way to express the intent. I do not want to configure thirty projects; I want to say "this tree is work".
Smallest useful scope
One inherited ModelSelection per directory, resolved by longest matching path prefix, with per-project settings taking precedence.
No new grouping entity, no per-folder settings UI tree, no allow-list of permitted models, no server or provider changes. Reuse the existing defaultModelSelection contract and the existing project-settings dialog; add the directory scope and the resolution step.
Alternatives considered
Risks or tradeoffs
- Resolution needs one clear rule. Longest matching prefix, project override wins, is the least surprising; anything fuzzier gets hard to reason about when trees nest.
- Path comparison has to stay consistent with existing normalisation — case sensitivity, trailing separators, and Windows separators.
normalizeProjectPathForComparison already handles this and should be the single source of truth rather than a second implementation.
- Symlinked or moved projects can fall outside the tree they appear to be in. Matching on the stored
workspaceRoot is predictable even if it occasionally surprises.
- The UI needs to show why a project's default is what it is — an inherited value that looks unset is worse than no feature. Showing "inherited from ~/Developer/Work" alongside the control would cover it.
Examples or references
Relevant existing surface:
Contribution
Before submitting
Area
apps/web
Problem or use case
My projects live in two trees that mean different things:
Everything under
Workshould start on my work provider account and its approved model. Everything underPersonalshould start on my personal one. The split is a property of where the project lives, not of any individual project.Today the default provider and model is a per-project field.
defaultModelSelectionalready exists on the project record (packages/contracts/src/orchestration.ts:225,:424,:1086),project.meta.updateaccepts it (:629,:639), and draft/thread creation already consumes it. So the mechanism works — but it has to be set once per project, by hand, and again for every new project I add.That does not scale past a handful of repos, and the failure mode is quiet: a new project under
Workinherits nothing, so the first thread starts on whichever selection was last used, which may be the personal account. The mistake is usually only obvious after the turn has already run.Proposed solution
Let a parent directory carry a default
ModelSelectionthat projects underneath inherit.~/Developer/Work), not just on a single project.workspaceRootup to the nearest ancestor that has a default configured; the longest matching path prefix wins, so~/Developer/Work/client-acan still override~/Developer/Work.defaultModelSelectioncontinues to win over any inherited one.Path prefix matching already has a home:
normalizeProjectPathForComparisonand the ancestor/prefix logic inpackages/client-runtime/src/state/projectGrouping.tsdo this kind of comparison for the sidebar's directory grouping today.Why this matters
It turns "keep work and personal accounts separate" from a per-project chore that silently fails on every new repo into a rule I state once. That matters for cost attribution, for subscription and rate limits landing on the right account, and for organizational boundaries where running a work repo through a personal account is a real problem rather than an inconvenience.
It is also the natural way to express the intent. I do not want to configure thirty projects; I want to say "this tree is work".
Smallest useful scope
One inherited
ModelSelectionper directory, resolved by longest matching path prefix, with per-project settings taking precedence.No new grouping entity, no per-folder settings UI tree, no allow-list of permitted models, no server or provider changes. Reuse the existing
defaultModelSelectioncontract and the existing project-settings dialog; add the directory scope and the resolution step.Alternatives considered
Risks or tradeoffs
normalizeProjectPathForComparisonalready handles this and should be the single source of truth rather than a second implementation.workspaceRootis predictable even if it occasionally surprises.Examples or references
Relevant existing surface:
defaultModelSelectionon the project record —packages/contracts/src/orchestration.ts:225,:424,:1086project.meta.updateaccepting it —packages/contracts/src/orchestration.ts:629,:639packages/client-runtime/src/state/projectGrouping.tsContribution