Summary
Two related issues found while debugging why sessions launched by a third-party terminal app (freshell, which pre-creates session stubs and launches amplifier resume <uuid>) behaved differently from directly-launched sessions. Diagnosis was validated by a three-round adversarial review with file:line verification.
Line numbers below refer to the installed release as of 2026-08-01 (amplifier-app-cli 0.1.1 via uv tool install).
Bug 1: resume/continue bundle resolution ignores bundle.active
Behavior: amplifier run consults settings bundle.active before falling back to the default (commands/run.py:134-141). The resume path does not: commands/session.py:112-129 extracts the bundle from session metadata and, when missing, leaves bundle_name = None; runtime/config.py:971 then hardcodes default_bundle = "anchors" without consulting settings. A call-site audit shows resolve_config's else-branch is reachable only from resume — run.py:164 always passes a resolved name — so the fix is contained.
Self-perpetuation: the resume flow then sets active_bundle = "unknown" (commands/session.py:153), which gets persisted into session metadata (incremental_save.py:112, main.py:2895/3581/3617). session_store.py:67-73 maps "unknown" back to None on the next resume — its docstring even promises "allowing caller to fall back to configured default bundle", i.e. the intended behavior is documented but not implemented.
Impact: any session whose metadata lacks a bundle (or carries "unknown") silently runs anchors forever, even when the user's ~/.amplifier/settings.yaml says bundle.active: foundation. Third-party launchers that pre-create session stubs (freshell) hit this for every session; users get a different bundle depending on how the session was started, with no error or warning.
Suggested fix:
- In
resolve_config's no-bundle branch, fall back to merged-settings bundle.active before the hardcoded "anchors" (mirrors run.py:134-141); update the "No bundle specified, using default" message to say when settings supplied the name. Placing it there also covers amplifier continue (same helper).
- Persist the resolved bundle name after fallback instead of the
"unknown" display string (deterministic resumes; pins the session).
- Keep
session_store.py's legacy "unknown" → None mapping permanently — it is the healing mechanism for already-affected sessions.
Note this is a (desirable) behavior change for existing "unknown" sessions: they will visibly switch from anchors to the user's configured bundle on next resume. Worth a changelog line.
Bug 2: delegate spawner never registers a system-prompt factory (defeats skills prefix placement; per-request token cost)
Behavior: session_spawner.py creates delegate children via AmplifierSession(...) directly (~line 486), bypassing foundation's PreparedBundle.create_session()/spawn(), and injects the agent persona as a static system message via context.add_message({"role": "system", ...}) (~line 741). There are zero calls to set_system_prompt_factory in the package.
Impact: the skills-visibility hook (amplifier-bundle-skills) defaults to placement='prefix', which wraps the context module's system-prompt factory so the skills index rides the provider's cached prefix. With no factory registered, every delegate child (and every recipe step — recipes use the same spawn path) logs a one-time WARNING to the shared terminal stderr and falls back to per-request injection: the multi-KB skills index is re-sent as fresh input tokens on every request of every child. In delegation-heavy sessions and long recipes this is a warning storm plus a real token cost.
Suggested fix (precedent: foundation's own PreparedBundle.spawn() already registers a factory for its children — amplifier_foundation/bundle/_prepared.py:869-881):
- Register a system-prompt factory for delegate children that replaces the static
add_message injection. Important: context-simple's get_messages_for_request filters out stored role=system messages when a factory is set (preserving only metadata.source == "hook" ones) — registering a factory alongside the static message would silently drop the persona. Replace, don't add.
- Guard registration on a truthy persona instruction (mirror foundation's
if effective_bundle.instruction or effective_bundle.context guard) — an unconditional empty-string factory would suppress stored system messages of personaless children for no benefit.
- Re-register the factory in
resume_sub_session (the persona is recoverable from the persisted agent_overlay metadata, ~line 817). Old transcripts containing the static persona dedupe automatically at request time (filtered while the factory supplies fresh content).
- The
spawn_mode="subprocess" branch routes before the injection code and goes through foundation's prepare path — excluded.
A companion issue on amplifier-bundle-skills covers the misleading warning text/level itself.
Summary
Two related issues found while debugging why sessions launched by a third-party terminal app (freshell, which pre-creates session stubs and launches
amplifier resume <uuid>) behaved differently from directly-launched sessions. Diagnosis was validated by a three-round adversarial review with file:line verification.Line numbers below refer to the installed release as of 2026-08-01 (
amplifier-app-cli0.1.1 viauv tool install).Bug 1: resume/continue bundle resolution ignores
bundle.activeBehavior:
amplifier runconsults settingsbundle.activebefore falling back to the default (commands/run.py:134-141). The resume path does not:commands/session.py:112-129extracts the bundle from session metadata and, when missing, leavesbundle_name = None;runtime/config.py:971then hardcodesdefault_bundle = "anchors"without consulting settings. A call-site audit showsresolve_config's else-branch is reachable only from resume —run.py:164always passes a resolved name — so the fix is contained.Self-perpetuation: the resume flow then sets
active_bundle = "unknown"(commands/session.py:153), which gets persisted into session metadata (incremental_save.py:112,main.py:2895/3581/3617).session_store.py:67-73maps"unknown"back toNoneon the next resume — its docstring even promises "allowing caller to fall back to configured default bundle", i.e. the intended behavior is documented but not implemented.Impact: any session whose metadata lacks a bundle (or carries
"unknown") silently runsanchorsforever, even when the user's~/.amplifier/settings.yamlsaysbundle.active: foundation. Third-party launchers that pre-create session stubs (freshell) hit this for every session; users get a different bundle depending on how the session was started, with no error or warning.Suggested fix:
resolve_config's no-bundle branch, fall back to merged-settingsbundle.activebefore the hardcoded"anchors"(mirrorsrun.py:134-141); update the "No bundle specified, using default" message to say when settings supplied the name. Placing it there also coversamplifier continue(same helper)."unknown"display string (deterministic resumes; pins the session).session_store.py's legacy"unknown"→Nonemapping permanently — it is the healing mechanism for already-affected sessions.Note this is a (desirable) behavior change for existing
"unknown"sessions: they will visibly switch fromanchorsto the user's configured bundle on next resume. Worth a changelog line.Bug 2: delegate spawner never registers a system-prompt factory (defeats skills prefix placement; per-request token cost)
Behavior:
session_spawner.pycreates delegate children viaAmplifierSession(...)directly (~line 486), bypassing foundation'sPreparedBundle.create_session()/spawn(), and injects the agent persona as a static system message viacontext.add_message({"role": "system", ...})(~line 741). There are zero calls toset_system_prompt_factoryin the package.Impact: the skills-visibility hook (amplifier-bundle-skills) defaults to
placement='prefix', which wraps the context module's system-prompt factory so the skills index rides the provider's cached prefix. With no factory registered, every delegate child (and every recipe step — recipes use the same spawn path) logs a one-time WARNING to the shared terminal stderr and falls back to per-request injection: the multi-KB skills index is re-sent as fresh input tokens on every request of every child. In delegation-heavy sessions and long recipes this is a warning storm plus a real token cost.Suggested fix (precedent: foundation's own
PreparedBundle.spawn()already registers a factory for its children —amplifier_foundation/bundle/_prepared.py:869-881):add_messageinjection. Important:context-simple'sget_messages_for_requestfilters out storedrole=systemmessages when a factory is set (preserving onlymetadata.source == "hook"ones) — registering a factory alongside the static message would silently drop the persona. Replace, don't add.if effective_bundle.instruction or effective_bundle.contextguard) — an unconditional empty-string factory would suppress stored system messages of personaless children for no benefit.resume_sub_session(the persona is recoverable from the persistedagent_overlaymetadata, ~line 817). Old transcripts containing the static persona dedupe automatically at request time (filtered while the factory supplies fresh content).spawn_mode="subprocess"branch routes before the injection code and goes through foundation's prepare path — excluded.A companion issue on amplifier-bundle-skills covers the misleading warning text/level itself.