amplifier-app-cli
microsoft/amplifier-app-cli/AGENTS.md
amplifierappcli/data/ ships inside the CLI's own wheel — anything placed there loads for every user, every session, version-locked to the installed CLI, with no bundle composition step in between. That reach is exactly why it must stay small. Before adding anything here, run it through these three tests, in order: 1. Does it depend on something the CLI uniquely provides, that cannot move? A slash command, a settings key, a terminal affordance — something with no home outside this process.…
AGENTS.md19 starsChanged 44 days ago
# AGENTS.md — amplifier-app-cli ## Boundary rule: `amplifier_app_cli/data/` vs. an external bundle `amplifier_app_cli/data/` ships inside the CLI's own wheel — anything placed there loads for every user, every session, version-locked to the installed CLI, with no bundle composition step in between. That reach is exactly why it must stay small. Before adding anything here, run it through these three tests, in order: 1. **Does it depend on something the CLI uniquely provides, that cannot move?** A slash command, a settings key, a terminal affordance — something with no home outside this process. If no → it belongs in an external bundle, not here. 2. **Would a non-CLI host ever want it?** If yes → external bundle. The CLI may still *include* it (compose the bundle), but must not *own* it — ownership belongs wherever the capability is portable to. 3. **Is the trigger unconditional?** If the asset is gated on settings, an env var, a flag, or runtime state, the asset itself may still live here, but the compose/injection *decision* stays in Python (see `runtime/config.py::_ensure_default_skills_dirs` for the pattern) — never encode conditional loading in a bundle YAML that lives alongside it. If the answer to 1 is "no" or the answer to 2 is "yes," it's an external bundle question, not a `data/` question. **Resolution rule:** assets under `amplifier_app_cli/data/` are always resolved by **package-relative path** (e.g. `Path(__file__).parent.parent / "data" / "..."`), **never** by git URI. A git URI decouples the asset's version from the installed wheel's version — defeating the reason for co-locating it here in the first place. If it needs independent versioning, it isn't a `data/` asset. **Token budget:** this location is auto-loaded for every user, every session — its budget discipline is stricter than anywhere else in the ecosystem. No always-on context files here without an explicit, named exception recorded in this section. Prefer mechanisms that load on demand (skills, agent-scoped context) over anything injected unconditionally. This section exists to keep `data/` from becoming a junk drawer — re-run the three tests before adding, not after. ## In-process self-child prompts Child configuration ownership: `agent_config.merge_configs()` returns a fully independent mutable mount plan, including untouched inherited values. Preserve merge/filter precedence, but never share nested session or module configuration with the parent or the agent overlay. The spawner applies budget and metadata updates in place; aliasing makes one child's limit become a sibling's default. Run `tests/test_agent_config.py` and `tests/test_spawn_config_isolation.py` when changing this boundary. For `agent_name: self`, `session_spawner` must build a new foundation prompt factory for the target child from the root prepared bundle, render it once, and install only that frozen result. Never copy or await a parent's installed context factory: hooks can wrap it with parent-specific state. Persist a nonempty resolved snapshot only in sub-session persistence metadata, never in `session.metadata` telemetry; keep subprocess self dispatch as its explicit legacy limitation. ## Root instruction AGENTS.md tail `load_and_prepare_bundle()` appends `@~/.amplifier/AGENTS.md` and then `@.amplifier/AGENTS.md` only after all behavior composition and before preparation. Do not move this seam: injecting earlier makes a bodyless root instruction truthy and changes its existing behavior-body inheritance. Keep the injection non-mutating because the foundation registry caches bundles; foundation's prompt factory resolves these optional files freshly per request. ## Bundle guidance ownership Document reusable capabilities as behavior bundles added with `--app` to the existing host. Document selectable roots separately; new-host examples use Anchors and preserve `@anchors:context/system.md` when their root has a body. This is documentation/help guidance only: do not change composition defaults or existing root selections to enforce it. ## Update reporting Keep report labels separate from update eligibility: a missing mutable cache is a download, not a newer revision, and an unchecked source is not current. Use the same word-status vocabulary in every `amplifier update` section, including verbose output. Keep the exact configured URI as the update action identity, but derive only credential-safe, globally unique display labels; malformed app entries render as a generic failed check and never leak their configured payload. Exercise the real Click command in `tests/test_update_reporting.py`; mock only status/apply boundaries so tests never touch a user's caches or installation. Module cache force-refresh identity is Foundation's exact URL + ref cache key, never a semantic bundle name or module entry-point ID. Verify it with `uv run pytest tests/test_module_cache.py`. ## Persisted reminder display `ui.is_displayable_session_message()` is display-only: history and replay hide only persisted transcript entries marked `ephemeral is True` and `persisted is True` whose content is a reminder envelope. Keep the original transcript and its source metadata intact so resume context restores the reminders; `reminder_placement` controls ordering, not display eligibility. ## Session metadata preservation Save paths preserving metadata from an earlier hook must use `SessionStore.get_metadata_if_exists()`; strict `get_metadata()` remains for user-requested lookups and must still fail for a missing session. JSON output paths must restore the console's configured backing stream, not a dynamic temporary stdout capture. Run-command diagnostics before headless execution belong on stderr so JSON stdout remains one parseable payload. ## Fork event ownership and cumulative cost Forks NEVER COPY parent root events, CI capture, or lifecycle metadata; parent CI is read only to build the boundary, and resume never reads ancestors. `--no-events` remains an accepted compatibility option only; event activity stays owned by its emitter. For a resumed transcript fork (identified by `forked_from_turn`, never `parent_id` alone), the child saves a versioned immutable `fork_cost_boundary`: inherited-turn cumulative Decimal totals, a canonical prefix fingerprint, and owner/fence provenance. Resume reads that verified boundary plus child-owned CI cost only; it never reconstructs ancestors. Honor configured CI relocation without falling back from a missing selected capture. An unavailable or pre-boundary fork is explicitly incomplete, never verified zero. Keep ordinary non-fork resume's CI-then-native fallback. Tests must use the Foundation dependency installed in their test environment. Do not prepend a neighboring checkout to `sys.path`: that silently bypasses the published dependency and lockfile. Install an explicit local override in the test virtualenv when cross-repository development requires one. ## Interactive slash completion Keep completion candidate generation in `ui/completion.py`. Its live-session snapshot is rebuilt only at REPL construction and immediately before each normal `prompt_async()` call; prompt-toolkit completion callbacks may read only that already-built snapshot. Do not add discovery, provider, filesystem, network, or configurator calls to a keypress path. Keep history search enabled: its prompt-toolkit compatibility path uses the public buffer insertion hook for safe, end-of-buffer leading-slash documents. Known argument candidates may open advisory menus; custom typed arguments must remain unchanged on Enter unless the user explicitly selects a candidate. Resolve the slash-popup UI setting once at interactive-session construction; it gates every automatic command and argument menu, outside prompt-toolkit callbacks and the per-prompt refresh loop. Unselected `Tab` must only accept a unique candidate or extend the literal longest shared candidate prefix; it never cycles choices. `Up`/`Down` and `Shift-Tab` create explicit reversible previews, which `Tab`, `Enter`, or `Space` may accept without submitting. Unselected command `Enter` accepts only an exact or unique command; unselected argument `Enter` always submits the exact current line, regardless of the automatic-popup setting. Keep ambiguous-completion guidance in the inline right prompt: prompt-toolkit hides bottom toolbars when a terminal cannot answer cursor-position requests. Verify its visible rendering in a CPR-unsupported PTY, not only its callback text. Pipe-input Escape tests must allow both VT-parser (`ttimeoutlen`) and key-binding (`timeoutlen`) timeouts. Shorten both on the test application, send one Escape, and wait for the restored document and closed menu; a second Escape is not a flush. ## Shell completion Click shell-completion callbacks may use only read-only local settings, registry metadata, and directory names/stats. They must not initialize keys or providers, migrate registries, construct `SessionStore`, read session content, or emit anything except Click's native completion protocol records. Keep lock-library imports at locking operations, not read-only module import: dependencies can run temporary-file capability probes when imported. Seed byte-preservation installer tests with explicit LF and CRLF bytes, not text-mode writes that translate newlines. Assert the original prefix survives the first install and the entire file is identical after a repeat install. ## Interactive control-flow exits REPL exit commands return an action from `CommandProcessor`; only the normal REPL loop may terminate so its shared `finally` runs cleanup and closes TTY input once. ## Highway watchdog regression The Highway watchdog records append-only `wake-needed` advisories only: it must not invoke Amplifier or consume markers, including triggers within its log verbosity gap. Keep its bounded fake-command tests proving each trigger records without starting host tmux or an Amplifier session.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

