feat(sdk): expose model profiles as synthetic gajae-code/<profile> models - #24
feat(sdk): expose model profiles as synthetic gajae-code/<profile> models#24snowykr wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8da5d1bf73
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a4c255480
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 66a79385f4
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fd84c54f8
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4673b585d0
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f82475bdb0
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92fa873412
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd550ac40a
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a215d53d6
ℹ️ 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".
d252c9a to
38ff34a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38ff34a185
ℹ️ 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".
38ff34a to
0a6067c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0a6067c473
ℹ️ 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".
7c4943a to
94c4f1c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94c4f1c6ea
ℹ️ 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".
94c4f1c to
1f9557f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f9557f176
ℹ️ 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".
1f9557f to
55f77b3
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 55f77b37e1
ℹ️ 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".
04f07e3 to
0dcd3e9
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0dcd3e9a0d
ℹ️ 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".
0dcd3e9 to
2ce26fd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ce26fdb2b
ℹ️ 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".
2ce26fd to
4c870c8
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c870c84de
ℹ️ 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".
0f49a3e to
2f2e5df
Compare
d11aefa to
109fa8b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d11aefa993
ℹ️ 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".
109fa8b to
7b38026
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b38026923
ℹ️ 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".
aeda94c to
ebb3808
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ebb380887e
ℹ️ 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".
ebb3808 to
40bd86a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40bd86ac60
ℹ️ 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".
40bd86a to
962b35c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 962b35cf0d
ℹ️ 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".
e4de5d3 to
3cd7353
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3cd7353c58
ℹ️ 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".
3cd7353 to
8491f2c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8491f2cbf7
ℹ️ 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".
8491f2c to
1f83d2d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f83d2d614
ℹ️ 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".
| if (this.#activeProfileInstalledRoles.has(role)) continue; | ||
| const bindingValue = bindings?.modelRoles?.[role]; | ||
| this.#activeProfileInstalledRoles.set( | ||
| role, | ||
| bindingValue ?? this.settings.getGlobal("modelRoles")?.[role as never], |
There was a problem hiding this comment.
Restore current durable roles when dropping a profile
When a session-scoped profile owns a role, this first-activation snapshot is never updated. If config.patch durably changes that same modelRoles entry while the profile remains active, a later concrete selection or session transition restores the old snapshot into the runtime override, masking the newly persisted value until restart; the agent-override loop has the same behavior. Remove the profile-owned key so the current lower settings layer becomes visible, then reapply configured bindings, rather than restoring a stale baseline.
Useful? React with 👍 / 👎.
…dels External ACP/SDK clients (notably the paseo TUI) could only use presets via the session-scoped startup Preset select, never as ordinary model choices, and could not persist a preset as the global default. Selecting a preset now behaves exactly like picking a model: Q10 models.list/current lists every availability-filtered profile as a logical gajae-code/<profile> row, and model.set on that namespace activates the profile live AND persists it to global modelProfile.default (mirror of `gjc --mpreset <name> --default`), with the same credential preflight, role clearing, flush, and rollback as the CLI path. The facade lives only in the Q10 projection and a shared resolver: no fake Model registry entries, no TUI / --list-models / /v1-models / coordinator / Q29 surface changes. Logical current derives exclusively from the in-session active-profile marker (never the persisted default), cleared only on successful concrete user/startup-override materialization (including the scoped, unscoped, and role cycle paths and the interactive setModel bindings); config.patch and activation serialize through the shared session admission boundary, with a structurally-compared shadow that invalidates profile-owned keys after activation while preserving unrelated patches; availability uses the Q27 authenticated-provider derivation with pattern-aware, managed-fallback-eligible default resolution, and degrades fail-closed on registry, join, or per-profile resolution errors. gajae-code is a reserved namespace with deterministic collision handling. Planned via ralplan (run 20260806-201255; Architect CLEAR pass 3, Critic OKAY pass 3) and executed under ultragoal with boundary-cohort gate and terminal critic OKAY. Verified: 247/247 affected tests, package typecheck, ACP core-v1 conformance 21/21, the four repo gates, and a live real-process ACP smoke (paseo-shaped) proving selection, global persistence, fresh-launch reapply, prompt execution on the profile default model, and non-leakage into --list-models. Lore-id: preset-to-model-sdk-facade Directive: keep gajae-code reserved; document any future namespace change Tested: Q10 synthetic projection, marker lifecycle, concurrency races, config.patch shadow freshness, registry-error fail-closed, ACP corpus, live ACP smoke Not-tested: manual paseo picker render on a real device
1f83d2d to
5a7aed9
Compare
|
Re: the profile-chain and role-free-binding threads — fixed in 5a7aed9. Dropping a session-only profile clears the profile-owned default fallback chain, and configured modelBindings are force-reapplied whenever a profile was active (including role-free default-only ones). Verified: package check and 263 focused tests pass. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a7aed9934
ℹ️ 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".
| const defaultChain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains.default; | ||
| if (defaultChain && defaultChain.identity !== undefined) { | ||
| this.setConfiguredModelChain("default", [], "user-selection"); |
There was a problem hiding this comment.
Preserve the successor session's fallback chain
When switching away from a session-scoped profile, switchSession() invokes this reset after setSessionFile() has loaded and resolved the successor session. This lookup therefore reads the successor's configuredModelChains.default, and any successor saved with its own profile-owned chain has that chain cleared and subsequently persisted by ensureOnDisk(), disabling its retries/fallbacks. Clear the chain before replacing the session context, or only clear it when its identity matches the profile marker being dropped.
Useful? React with 👍 / 👎.
| if ( | ||
| definition.valueSchema.type === "model-selector-value" && | ||
| !(typeof entry === "string" || (Array.isArray(entry) && entry.every(item => typeof item === "string"))) | ||
| ) { |
There was a problem hiding this comment.
Reject empty model-selector values in config patches
For an SDK patch such as { modelRoles: { default: [] } } or { modelRoles: { default: "" } }, this check accepts the value because an empty array vacuously contains only strings and strings are not checked for blank content. The newly enabled SDK-only config.patch path then persists a value that violates the model-selector contract; downstream normalization treats it as no selector, silently disabling the configured default routing. Validate these entries with the existing isModelSelectorValue() predicate, which enforces nonblank strings and nonempty chains.
Useful? React with 👍 / 👎.
The canonical replacement-receipt ENOENT guard had no regression at the exact readdir-to-open window, while the legacy test deleted its receipt before enumeration and never exercised the catch. Inject disappearance from openSync so both canonical and legacy tests prove mutation continues only for benign absence. Lore-id: canonical-receipt-preopen-enoent Constraint: preserve fail-closed handling for identity mismatch and non-ENOENT failures Confidence: high Scope-risk: narrow Reversibility: trivial Tested: bun test packages/coding-agent/test/session-storage.test.ts -t replacement cleanup receipt reconcile TOCTOU resilience (4 pass) Tested: bun --cwd=packages/coding-agent run check Not-tested: full session-storage file clean; 4 unrelated writer-security failures remain in the local native build
Yeachan-Heo#3994) A managed session scope is snapshotted in full on every session start, and the snapshot fails closed once the tree exceeds the managed byte budget. The scope is filled by GJC's own session records — tool logs, subagent transcripts, artifacts — so a working directory in sustained use crosses the budget without the operator doing anything unusual. There is no signal before that happens. The first symptom is a launch that aborts, and `gjc gc` (the command that already reports state the operator cannot otherwise see) says nothing about it. On the machine this was found, the scope reached 577 MiB against a 512 MiB budget with no prior warning. Report scope usage from `gjc gc` once a scope is at or past 75% of the budget. The probe only measures; nothing in the prune path acts on it, and `gc` still reclaims no session records. Deliberately conservative so existing runs are unaffected: - Scopes below the threshold are omitted, so output is byte-identical for anyone not near the budget. - Absent / non-directory / unreadable scopes report `unavailable` rather than failing the run. - An unreadable subtree is skipped and the walk continues, because a partial total still answers "am I near the budget?". - The walk is bounded, and a truncated walk is marked so the total is read as a floor rather than a measurement. Refs Yeachan-Heo#3959. The capacity problem itself — no retention policy, and `gjc gc` unable to reclaim session records — needs a maintainer decision and is left out of this change. Tests: packages/coding-agent/test/gc-session-scope.test.ts (6 new)
…ssion Review of Yeachan-Heo#3988 found the advertised session-scoped guarantee did not hold when no durable modelRoles.default exists -- the default state for the ACP-only clients this feature targets. resolveConfiguredDefaultModel() resolves with currentModel=undefined, so it returns undefined and the restore guard in #initializeNewSessionState was a no-op, leaving /new to record the profile's model as the successor's. Two sibling gates read the merged settings layer where they authorize durable global writes. Lore-id: 3f9c21ab Constraint: the pre-profile model must be snapshotted by the caller -- activation replaces the runtime model before noteProfileInstalledOverrides runs Constraint: first activation wins so chained session-only profiles restore the original selection, not the previous profile's Rejected: read this.model inside noteProfileInstalledOverrides | setModelTemporary already overwrote it, so it captures the profile's own model Rejected: change resolveConfiguredDefaultModel to accept a current model | the TUI resume flow depends on its current semantics Confidence: high Scope-risk: narrow Reversibility: easy Tested: /new after a session-only profile with no durable default restores the pre-profile model and writes no modelRoles Not-tested: rollback promotion under a concurrent project-config write
Problem
External ACP/SDK clients (notably the Paseo TUI, registered as a generic ACP provider) can discover GJC's model catalog through the ACP
Modelselect, but model presets were only reachable through the session-scoped startup--mpreset/Q27Presetselect — never as ordinary model choices — and there was no way for a client to persist a preset as the global default. Users of paseo-style clients had to fall back to the TUI for preset configuration.Change
The SDK
models.list/current(Q10) catalog now lists model profiles as logical synthetic models under the reserved namespacegajae-code/<profile>(e.g.gajae-code/codex-eco, displayed as "Codex Eco"). Selecting one throughmodel.set(or the ACPModelpicker) activates the profile in the live session and persists it to the globalmodelProfile.default, mirroringgjc --mpreset <name> --default— with the same credential preflight, role clearing, settings flush, and rollback as the CLI path.Key semantics:
sdk/model-profile-model.ts). No fakeModelregistry entries; no TUI/model,--list-models, auth-gateway/v1/models, coordinator MCP, or Q29 provider-row changes. Verified non-leakage:gjc --list-modelsshows nogajae-coderows.current/config.list/getmodelderive exclusively from the in-session active-profile marker (session.getActiveModelProfile()), never the persisted default; the marker is cleared only on successful concrete user/startup-override materialization (incl.setModelTemporaryForControl) and never on internal fallback/restore/rollback (activation's ownprofile-activationcause never self-clears). A persisted-but-inactive default never creates a synthetic current row.model.setwithgajae-code/*validates/canonicalizes the suffix (lossless after the first slash, legacy alias support), rejects non-offthinking levels asinvalid_inputbefore admission, and routes throughactivateModelProfile({ persistDefault: true })inside the shared session admission queue.config.patchis serialized through the same admission boundary so a patch racing an activation is never lost or clobbered.models.ymlprovider namedgajae-codefails closed (rows omitted, selection rejected) instead of being silently shadowed.invalid_input(ACPinvalidParamsvia the existing mapping); missing profile credentials →authentication_failed(authRequired); the no-thinking result envelope is exactly{ changed: true }, the typed result is{ provider: "gajae-code", modelId, thinkingLevel }.User-visible behavior
config.list/getreport the synthetic id as current (gajae-code/<profile>,currentThinkingLevel: "inherit"); the existing ACP startup--mpreset/Q27Presetselect stays session-scoped and unchanged.docs/sdk.md,docs/external-control-readiness.md(Paseo section),docs/sdk-app-guide.md,docs/models.md;CHANGELOG.mdentry added.Validation
sdk-model-profile-model.test.ts; extendedsdk-q10-models,sdk-control-dispatch,sdk-host-wiring,sdk-default-model-selection-e2e), including concurrency races (two concurrent selections FIFO,config.patchracing a selection) and registry-error fail-closed.bun --cwd=packages/coding-agent run check:typesclean; packagecheck(biome + tsc) clean.verify-g002-gates.ts,rebrand-inventory.ts --strict,check-visible-definitions.ts,default-gjc-definitions.test.tsall pass;check:schemasclean; docs-index regenerated in sync.acp-core-v1corpus (acpx 0.13.0) 21/21.gjc acpprocess with an ACP client (same protocol paseo uses): the Model config options containgajae-code/custom-eco;session/set_config_optionswitches the session; a prompt turn runs on the profile's default model;modelProfile.defaultis persisted in the realconfig.yml; a freshgjc acplaunch reapplies it;--list-modelsshows nogajae-coderows.Related work
No existing open PR targets this surface (checked
gh pr list --head preset-to-model). This is additive to the ACP preset-catalog work (Yeachan-Heo#3922) — it extends the same Q10/ACP pipeline from "filter the model catalog" to "offer presets as selectable models that persist globally". No duplicate; complements the existing startupPresetmode rather than replacing it.Checklist
dev