Repository navigation
feat(profiles): support session-bound active profiles without global routing mutation #1064
Description
Before submitting
- I searched open and closed issues and did not find a request for this feature.
- I reviewed this request and removed credentials, tokens, private paths, hostnames, and other sensitive data.
Problem or opportunity
Concurrent Pi processes may need different active profiles while sharing the same machine or project. Today, /gentle:profiles updates shared configuration and materialized routing by default. Running work remains unchanged, but later subagent launches can observe another process's profile changes.
This affects ordinary terminals, terminal multiplexers, IDE panes, CI, and other environments. It is a Pi process/session configuration boundary rather than a host-specific integration concern.
Proposed outcome
Preserve the existing global profile mechanism and add an explicit, opt-in active-profile binding at the parent Pi session boundary.
The profile UI should distinguish actions conceptually equivalent to:
- Use in this session — resolve and store a session-local profile binding and effective routing snapshot without mutating shared routing files.
- Reload in this session — explicitly refresh that session's snapshot from the saved profile definition.
- Set as global default — retain the existing global persistence and materialization behavior for future or unbound sessions.
Session-bound behavior should satisfy these invariants:
- Concurrent parent sessions can select different profiles without rewriting each other's shared defaults or routing.
- Switching profiles within one parent session updates only that session's binding.
- New child launches inherit the parent's current effective snapshot.
- Running and already-queued children retain the routing resolved when they were launched or queued.
- Resuming a parent session restores its last session-bound profile and snapshot.
- Editing a saved profile definition does not silently alter an already-bound session; refreshing it requires an explicit reload or re-selection.
- Display the effective profile, scope, and configuration source.
- Existing behavior remains unchanged when no session binding is selected.
- Existing profile files require no migration.
A possible precedence model is:
explicit launch-local route
→ parent session-bound profile snapshot
→ project routing
→ global active profile
→ existing defaults
The exact command names and persistence mechanism can follow the maintainers' preferred configuration authority. The central invariant is that session-scoped selection must not mutate routing observed by unrelated concurrent parent sessions.
Suggested regression coverage:
- two concurrent parent sessions using different profiles;
- concurrent sessions in the same project;
- switching one session while the other remains unchanged;
- queued and running child stability;
- future child inheritance after switching;
- session resume;
- explicit session reload after editing a saved profile;
- unchanged behavior for sessions without a binding;
- existing profile files loading without migration.
Relationship to the existing per-repository pins
The per-repository pin shipped in #1074 solves a narrower configuration boundary:
pwrites a clone-local selection to<git-common-dir>/gentle-ai/profile-pin.json. It is isolated from other clones, but every worktree and every Pi session in that clone shares it.Pwrites a repository declaration to<worktree-root>/.pi/gentle-ai/profile.json. It is intended as a shared repository or team default, so it is also shared by concurrent sessions using that worktree.- Both layers affect subagent routing at launch without rewriting the globally active routing. The clone-local
player takes precedence overP, then routing falls back to the globally active profile.
Those pins solve parallel work across different repositories or clones. They do not allow two parent sessions in the same clone to select different profiles. If session A changes p, future child launches from session B observe the same clone-local selection. P is likewise a repository default, not a session override.
This request adds an independent layer above those defaults. A parent session should hold its own effective profile snapshot, children should inherit the snapshot of the parent that launched or queued them, and another session should remain unchanged. Because it is a snapshot rather than a shared profile-name lookup on every launch, later edits to the saved profile definition should require explicit reload or re-selection before affecting that bound session.
The resulting conceptual precedence is:
explicit launch-local route
→ parent session-bound profile snapshot
→ clone-local pin (`p`)
→ repository declaration (`P`)
→ global active profile
→ existing defaults
The existing p and P behavior should remain unchanged. They become defaults for sessions that have no explicit session binding.
Alternatives considered
Separate GENTLE_PI_CONFIG_HOME and GENTLE_PI_AGENT_HOME / PI_CODING_AGENT_DIR roots provide supported path isolation, but require external environment setup and duplicated configuration rather than a native session binding.
Project-local routing helps separate projects but does not solve concurrent sessions in the same project. Switching the shared active profile is not session isolation.
Additional context
Related: #1025 proposes authorized routing classes selected for individual child launches. This request concerns a human-selected active profile bound to the parent Pi session and inherited as its default routing context. The two features can coexist if launch-local routes have explicit higher precedence.
Coordinate precedence with #600 and preserve the path-isolation support established by #575 and #633.
Activity
JeanN00B commentedon Sep 16, 2026
Additional concurrency finding from a source and runtime audit:
The global scope is documented and should remain backward-compatible, but concurrent profile operations are not serialized.
profiles.jsonand Pi orchestrator settings use per-file temp-and-rename writes, whilemodels.jsonis written directly. Applying a profile spans the profile store,models.json, materializedsubagents.jsonor agent frontmatter, and Pi settings without a cross-file lock or transaction. Two processes can therefore read stale panel state, overwrite each other's active marker or routing, or leave the stores representing different profiles.ppidoes not solve this by itself because it only setsPI_CODING_AGENT_DIR; Gentle'sGENTLE_PI_CONFIG_HOMEremains shared, and same-cwd project routing remains shared too.A narrow disposable two-process repro confirmed the shared-store behavior: process A wrote profile A, process B read A; after B wrote profile B, A read B. Using distinct
GENTLE_PI_CONFIG_HOMEvalues kept the stores isolated. Suggested coverage includes concurrent apply, create, update, and rollback operations, stale panel state, and shared versus isolated config homes. This is distinct from the session-bound behavior requested here, but likely worth considering in the same design.daandradec commentedon Sep 16, 2026
+1 on this — here is a concrete workflow that needs the session-bound invariant, in case it helps with prioritization.
Use case
I run two
pisessions side by side in two completely separate project directories, with two profiles created via/gentle:profiles: one routed to OpenAI, one to DeepSeek. Project A is the OpenAI work, project B is the DeepSeek work.What I need from this feature is exactly one of the invariants already listed: selecting the DeepSeek profile in session B must leave session A on OpenAI. The two that matter most for this workflow are:
What happens today
The
activemarker is machine-global, so choosing a profile in one session changes the routing that every other concurrent session resolves for its next subagent launch. In the two-project setup above, after activating DeepSeek in project B,/gentle:modelsin project A had moved to DeepSeek as well.The per-repository pin shipped in #1074 (
pon a profile) covers the two-separate-repositories case and is the right workaround today. It does not cover the general case this request is about: concurrent sessions that share one repository, or a single session where I want a different profile than the repository default without touching shared state.Environment
@earendil-works/pi-coding-agent)v2.5.0-179-g8ec0f98b(package.json 2.5.0)No new mechanism to propose — the precedence model in the description already handles this case. Happy to test a branch once session binding lands.
LCubero commentedon Sep 17, 2026
Confirmed a Windows reproduction that adds one specific failure mode to this request:
/gentle:profilespersists the profile'sorchestratorentry to Pi's globaldefaultProvider,defaultModel, anddefaultThinkingLevel, but it does not update the model or thinking level of the current parent session.The paths resolve correctly on Windows, with no agent/config-home overrides and no project-local
.pi/settings.json, so this does not appear to be a Windows path issue. The live session state can diverge from bothsettings.jsonand the active profile. Also,ssnapshots the orchestrator throughreadOrchestratorSettings(...), which captures the global defaults rather thanctx.modelandctx.thinkingLevelfrom the running session.For the proposed Use in this session behavior, please include both sides explicitly: capture the live parent model/effort from the session, and apply the selected profile through Pi's current-session APIs (
pi.setModel()andpi.setThinkingLevel()). Persistingdefault*should remain the separate global-default action.AitorDuran commentedon Sep 27, 2026
A concrete parallel-work scenario for this request:
The reported behavior is that selecting a profile in one session changes the effective profile in other already-running sessions, even across projects. That makes it impractical to run these independent model/provider mixes in parallel, although each session can already use a different orchestrator.
Desired behavior: selecting a profile changes only the current session's effective model routing. Other live sessions should retain their chosen profiles regardless of whether they share a repository, belong to separate worktrees, or run in unrelated projects. Any repository-level default for new sessions should not silently rewrite existing sessions.
Denver2828 commentedon Sep 27, 2026
Adding a concrete 3.7.0 use case for this session-bound routing proposal: please distinguish saved defaults from the dispatching parent's live model and thinking level. This is a UX/contract clarification and opt-in enhancement request, not a confirmed regression.
Observed with Gentle Agents (
subagent_run): the parent session recordedopenai-codex/gpt-6-astrawithlowthinking before dispatch, but an unpinnedgentle-ai-verifylaunched withopenai-codex/gpt-5.5/medium, matching the saved Pi default. No verifier model assignment was present in the agent definition or saved routing. Child runtime metadata confirmed the difference.The installed 3.7.0 implementation explains the result:
resolveAgentProfileresolves profile, then agent definition, then configured defaults. It does not take the parent's live model as an input.childArgumentsomits--modelwhen unresolved, leaving the child to use Pi startup defaults.For the design here, could we make these separate, user-owned choices: use saved routing/defaults (unchanged existing behavior) and inherit the parent session's live model + thinking (explicit opt-in)? A session-bound profile snapshot and a live
/modelselection are not necessarily the same thing, so their interaction and precedence should be stated rather than inferred. Explicit agent assignments should remain predictable, with the resolved provider/model/thinking and source visible before execution. The intended benefit is predictable cost and capability, not forcing every role onto one model.Suggested coverage: parent selection differs from saved defaults; explicit role assignments; parent changes after queueing (queued tasks retain their resolved snapshot); and unchanged behavior without opt-in. This can fit the design discussion in this issue alongside #1025 rather than creating another overlapping tracker. No claim is made that current main has been reproduced or that the existing behavior violates a documented live-inheritance contract.
matraket commentedon Sep 29, 2026
I ran into this problem while working with two sessions in the same repo: one on a frontier-model profile and one on a local/open-source profile to translate a documentation corpus. I spent quite some time analyzing the best way to solve it, and along the way I came across this issue and its comments, as well as #1240 and #1478, which tackle the same problem from different angles.
This comment is an attempt to consolidate my approach with the issue and with everything contributed in the thread. To avoid arguing from assumptions, I traced the code on current
main(5cc591f) and on Pi 0.87.1. Everything below is verified against that code unless I say otherwise.Where I agree and where I don't
I agree with @noxsystems on the central invariant: selecting a profile in one parent session must not change what any other session launches. I also agree with the precedence (launch-local route, then session, then
p, thenP, then global), with children inheriting the parent's selection, with running and queued children keeping what they resolved (this already holds today: the queue stores the fully resolved request,agents-runner.ts:349), with resume restoring the selection, and with no migration of existing files.I disagree with @noxsystems on making the session binding opt-in. If Enter keeps mutating global state and "use in this session" is a separate action, the default path is still the one that silently moves every other session. The
pandPkeys already establish the right mental model: Enter picks a profile for this session, and the persistent layers are explicit keys. I propose that Enter becomes session-only and that a newGkey sets the global default, next top(clone) andP(repository).On snapshot versus following the profile by name, I don't have a settled position. I see arguments for both. Simplicity favors storing only the name: in my opinion, the most common reason to edit a profile is tuning it (an agent is under- or over-powered), and in that case it makes sense for every session using it to get the adjustment without a reload in each one. Avoiding silent changes favors the snapshot + reload that @noxsystems proposes. I think both options are reasonable and only real usage will tell which one was right, so either works for me. What does seem important to me is not letting this decision block the rest: it is a minor detail compared with the current situation, where it is practically impossible to parallelize work that needs different capability levels. Whichever option is chosen, children that are already running or queued are unaffected (this already holds today), and if the bound profile is deleted, the session falls back to the next layer.
I agree with @JeanN00B that the profile stores are not serialized. With this proposal the only shared writes left are explicit (
G,p,P, create/edit/rename/delete), so there are few of them, but they still need to re-read before writing (today the panel writes back an in-memory copy ofprofiles.json), a lock, atomic writes formodels.json, and rename retries on Windows (EPERM/EBUSY).I agree with @daandradec that
pis the right tool for separate repositories and does not cover sessions in the same clone. One detail to add: today, when a pin exists, Enter rewrites the clone-local pin (gentle-ai.ts:4122-4128). In other words, in a pinned repo, one session's Enter changes the profile of every session and worktree of that clone. I believe this is exactly the case I ran into.I agree with @LCubero that "use in this session" should go through
pi.setModel()/pi.setThinkingLevel()and that persisting Pi'sdefault*settings is a separate concern. Since #1220 the live switch exists (switchLiveOrchestrator), and a2e1f22 / #1522 fixed the live capture foru. What remains is that applying a profile also writessettings.json(gentle-ai.ts:4238), which changes the starting model of every future session. I propose removing that write entirely (see "Orchestrator" below).I agree with @AitorDuran, including the last point: a repository default must not rewrite sessions that are already running. In this proposal each session resolves its profile once, at start, so changing
p,PorGonly affects sessions started afterwards.I agree with @Denver2828, with one nuance. A role the profile does not assign should not fall back to whatever global default Pi has at that moment. Inside a profile, "not assigned" should mean "inherit the parent session's live model and thinking". I would make this the default behavior for sessions with a profile, not an opt-in, because a profile is meant to be a complete snapshot (#916): an omitted role inheriting an unrelated global default is exactly the kind of leak this issue is about. Sessions with no profile at all keep today's behavior.
I agree with #1240 that the review lens path must be covered. Lens captures read
models.jsondirectly (gentle-ai.ts:6759,:7587), so today they ignore even the repository pin.Consolidated proposal
1. Panel actions
p<git-common-dir>/gentle-ai/profile-pin.json(unchanged)P<worktree-root>/.pi/gentle-ai/profile.json(unchanged)G(new)activeinprofiles.jsonEnter never writes pins,
models.json, agent frontmatter,subagents.jsonorsettings.json.2. Resolving the profile when a session starts
flowchart TD S[session_start] --> C{Is it a<br/>subagent?} C -- yes --> X[Do nothing<br/>the parent passed --model] C -- no --> E{Session has a<br/>profile entry?} E -- yes --> R[Restore the profile<br/>orchestrator: Pi restores it] E -- no --> P[Resolve profile<br/>p → P → G] P --> N{Session has<br/>messages?} N -- yes --> W[Bind profile<br/>keep orchestrator] N -- no --> M{Explicit<br/>--model?} M -- yes --> W2[Bind profile<br/>keep CLI model] M -- no --> O[Bind profile<br/>apply its orchestrator]Three details that matter for the implementation:
reasoncannot tell a new session from a resumed one. Pi creates the initial runtime without asessionStartEvent, sopi --resume,--continueand--sessionall arrive asreason: "startup", the same as a brand new session (Pi 0.87.1,dist/main.js:681andcore/agent-session.js:166). The decision has to come from the session's content.gentle-ai'ssession_starthas no guard for them today (gentle-ai.ts:9221). Without that guard, every subagent would replace its--modelwith the profile's orchestrator.--modelwins. Pi applies the CLI model to every runtime it creates,/newincluded, so the extension should respect it too./forkneeds no special handling: Pi copies every entry on the branch (labels excluded) into the forked session, so the profile entry travels with it./newresolves again fromp/P/G, which is the same thing Pi does with the model itself.3. Launching a subagent
flowchart TD L[subagent_run<br/>subagent_continue] --> A{Launch-local<br/>route #1025?} A -- yes --> RT[Use the route] A -- no --> B{Profile assigns<br/>this role?} B -- yes --> PR[Use the<br/>profile entry] B -- no --> PL[Inherit the parent's<br/>live model and thinking] RT & PR & PL --> Q[Resolve and queue<br/>queued and running keep it]This generalizes what the pin already does at launch (
withPinnedModelProfiles,gentle-agents.ts:1011): from "the pin" to "the session's profile". Materialized frontmatter andsubagents.jsonstop being read for sessions with a profile, which removes the one-launch lag and any cross-session effect through the materialized stores. Lens captures resolve through the same session profile. When a session delegates into another repository (workspace_root/repository_root), the session's profile still wins over the target repository's defaults.4. Orchestrator (and #1478)
Enter only switches the live orchestrator. New sessions get the profile's orchestrator in step 2, so writing Pi's
defaultModel/defaultThinkingLevelis no longer needed, andGdoes not writesettings.jsoneither. Anyone who wants a Pi-wide default still has the "set as default" option in Pi's own/model.@danielgap, this overlaps with point 4 of your #1478 design ("apply side unchanged"). The stored contract you describe (explicit value or leave-untouched) fits this proposal as is: leave-untouched simply means step 2 never touches the orchestrator. The only conflict is the
settings.jsonwrite on apply. Would you be open to leaving the apply side out of #1478 so it can change here, or to aligning both designs before either PR lands?5. Compatibility
activekeeps its shape and now means "global default for new sessions".docs/readme-reference.md.6. Suggested slicing
The first three are independent fixes and can land before the design discussion is settled.
Suggested regression coverage
--resume,--continue,--session) restores the bound profile and does not touch the orchestrator.p,PorG; not applied with an explicit--model./forkkeeps the profile;/newresolves again from the defaults.G/p/ create operations do not lose each other's writes, including rename contention on Windows.Happy to help with any of these slices once the direction is agreed.
matraket commentedon Sep 29, 2026
@noxsystems @JeanN00B @daandradec @LCubero @AitorDuran @Denver2828
I've opened a post in the gentle-shell channel on Discord to discuss this topic more directly. I'd appreciate it if you could stop by when you have a moment.
barbatdev commentedon Sep 29, 2026
Approved as an umbrella for session-bound profile selection.
Please keep this as reviewable slices instead of one large PR. I think the first slice should be the smallest end-to-end path:
Enterin/gentle:profilesbinds the selected profile to the current parent session.name (session).p → P → global.p), repository declaration (P), global routing, materialized stores, agent frontmatter, or Pi default settings.Follow-up slices can cover explicit reload/edit/delete semantics, orchestrator/default handling, lens/reviewer routing if needed, and concurrent store-write hardening.
Out of scope for the first PR: global locking/transactions, Windows rename retry hardening, and broader profile-store serialization.
barbatdev commentedon Sep 29, 2026
Approved note above still stands. One extra clarification after checking current
main: Usage now follows the effective profile to decide which providers to refresh/show.That means the session-bound layer should plug into that same effective-profile resolution, not only into subagent launch routing. Otherwise children could launch with a session profile while Usage still reports providers from the local/repo/global profile.
So for the first slice, please treat the shared precedence as:
session → local pin (p) → repository declaration (P) → globaland use that effective source for subagent launch routing, header/status display (
name (session)), and Usage provider scope.danielgap commentedon Sep 29, 2026
Taking the first slice as scoped above:
Enterin/gentle:profilesbinds the selected profile to the current parent session, shown asname (session).p → P → global; no mutation of pin, declaration, global routing, materialized stores, frontmatter, or Pi defaults.session → p → P → globalfor launch routing, status display, and Usage provider scope per the clarification above.I will keep it test-first and post the slice plan here before opening the PR.
danielgap commentedon Sep 29, 2026
Slice plan for the first PR, mapped to the approved scope:
Shared state — new
lib/session-profile-binding.ts: an in-process map keyed by parent session id holding{ name, modelProfiles }(the normalized routing snapshot taken from the saved profile at bind time, defensively copied on read/write). Concurrent Pi processes are isolated by construction; resume/restore and reload semantics stay for follow-up slices.Enter binds the session (
/gentle:profiles): Enter stores the session binding for the selected profile under the current parent session id. It writes nothing: no store active marker, nomodels.json, no materialized stores, no agent frontmatter, no Pi settings, and no pin or declaration changes, pin present or not. The current global-apply behavior (including its pin-aware repo-scoped re-pin) moves to a new explicitakey ("set as global default"), unchanged in semantics.Launch routing (
gentle-agents): the launch resolver becomessession → p (clone pin) → P (repo declaration) → global. A present session binding replacesmodelProfileswholesale for launches from that parent session, exactly like a winning pin does today. Queued and running children keep the routing resolved when their task request was created.Display: the shell bar and the profiles panel show the effective profile as
name (session)when a binding is present, ahead ofpin (source)and the global active name.Usage provider scope: usage refresh covers the providers named by the session binding's routing (distinct
provider/prefixes of its model ids) alongside the live provider, so the panel meters what the session actually launches with.Tests, written first: binding store (isolation per session, snapshot immutability); launch precedence session > pin > global and queued-request retention; reader precedence and the
(session)label; panel Enter = session bind with zero file writes (spied),a= legacy global apply; existing unbound behavior untouched.Out of scope per the approval note: reload/edit/delete binding, orchestrator/default handling, lens/reviewer routing, persistence across resume, store-write hardening.
noxsystems commentedon Sep 29, 2026
Thanks for taking the first slice, @danielgap. I won't open an overlapping PR. I can review and test it when it's ready, especially two parent sessions in the same clone, zero shared or pin writes on Enter, queued-task stability, and agreement between child routing, status, and Usage on the effective profile.
I have a local prototype with persisted session snapshots and resume/fork tests. Its UI keys and per-field fallback differ from the approved first slice, so I would adapt the relevant pieces rather than cherry-pick it. After the first slice lands, would a separate, narrowly scoped persistence/resume follow-up be useful? Happy to coordinate its contract before starting.
danielgap commentedon Sep 29, 2026
Thanks @noxsystems, the chain is ready for you:
name (session), the legacy global apply moves toa. All checks green, CodeRabbit clean.session → p → P → global, the shell status label, and the usage provider scope.On your four scenarios, so you know what is already pinned by tests versus what would benefit most from your live testing:
models.json, materializedsubagents.json, and both pin layers stay byte-identical, including with a winning pin present).On the persistence/resume follow-up: yes, that would be useful and it is the natural next slice. The store is deliberately in-process for slice 1 (
lib/session-profile-binding.ts), and everything reads it through one seam, so persisted snapshots could plug into the same read path without touching the three consumers again. Coordinating the contract first is the right call, and it should probably line up with barbatdev's follow-up list (reload/edit/delete semantics in particular, since persistence and reload rules shape each other). Happy to sync on the contract whenever you start.danielgap commentedon Sep 29, 2026
Recording two test-debt items from the native review of slice 2 (#1558, lineage review-0cb9827ae718d863, both informational advisories) so they stay visible for the follow-up slices:
Plan: cover both in the next slice that touches this area, or a small dedicated
test:PR under this umbrella if it lands first.AlejandroTatum commentedon Sep 30, 2026
Adding a real-world data point for this request.
I work on several repositories in parallel, each in its own Pi session, and each wants a different routing profile: one on a Claude-only profile, another on a GPT/GLM profile, everything else on the global default. Today that takes three separate mechanisms, each covering only part of it:
.pi/settings.jsonfor the orchestrator, since the pin never moves it (feat(models): explicit orchestrator model/effort selection in /gentle-models so profiles stop silently overwriting the live session #1478),When any of the three drifts, the session quietly runs on the global profile. The only way I found to notice was reading the subagent session JSONL files. It happened often enough that I stopped trusting what I thought each session was running.
What would remove the friction: the profile chosen when a session starts stays bound to that session — for the orchestrator and every subagent it launches — regardless of what another session activates later. Happy to test the session-binding work (#1557/#1558) against this setup.
86 remaining items
matraket commentedon Oct 23, 2026
@barbatdev starting the 3c read-side slice. One finding changes how the
/gentle:modelspart can stay read-only, so flagging it before I code it./gentle:profilesis straightforward: its effective-routing table goes throughresolveSessionProfile, so it shows the Enter binding, the frozen profile and the pin./gentle:modelsis an editor, not just a view: its rows are the draft thatCtrl+Swrites to the global routing. If the rows were seeded from the session's profile, saving would silently copy that whole profile into the global routing, for example after changing a single thinking level. That is a write-side change by another route, which is what you asked to keep out.So unless you prefer otherwise, I'll keep the rows as what the panel edits (the global routing) and make the session visible:
balanced-gpt (session).launches: openai/gpt-….The tests pin both reported cases through the public render: with Enter and with a pin, the panel shows what launches use.
The header and the
/gentle:profilesfix stay as they are once D2 lands. The per-row marks are a bridge until then: once/gentle:modelsedits the session's profile they always match, and they go away.I'll sync with @noxsystems on the 3b-i overlap in Discord.
barbatdev commentedon Oct 23, 2026
Good catch, and yes, I prefer your reading. Seeding the editor rows from the session profile would turn the next Ctrl+S into a silent copy of that profile into the global routing, and that is exactly the write-side change I wanted to keep out of this slice. The header with the session's launch profile and layer plus the per-row launches marks give users the visibility the reports are actually asking for, without touching what the panel edits. Go ahead with that plan.
One adjustment on issue closure: this slice closes #2451 but should not auto-close #2276. Efirvida's report explicitly asks /gentle:models to display and edit the session profile's agent models, and the edit half is D2. Reference #2276 as resolved on the display side with D2 still open, so the expectation is set correctly.
feat(profiles): add session profile disk corroboration reader (#1922)
feat(profiles): add session profile append controller with failure qu…
fix(profiles): keep uncorroborated appends untrusted after disk recovery
test(profiles): cover a throwing post-append stability recheck
feat(profiles): add session profile controller lifecycle seams
feat(profiles): add session profile lifecycle integration adapter
test(profiles): cover session profile selection serialization helpers
feat(profiles): gate subagent launches on session profile authority
fix(profiles): decide profile-less branches before reading the sessio…
feat(profiles): persist Enter session profile selection and wire life…
fix(profiles): skip the Enter notice when its ctx went stale while qu…
feat(profiles): add /gentle:profiles clear and unreadable-record warning
fix(profiles): treat unsaved never-bound sessions as nothing to clear