Skip to main content

agent_profile/adapter/
amp.rs

1// Copyright (c) 2026 Costas Kirgoussios
2//
3// This software is licensed under the PolyForm Noncommercial License 1.0.0.
4// You may obtain a copy of the License at:
5// https://polyformproject.org/licenses/noncommercial/1.0.0/
6
7//! Amp CLI: `--settings-file <profile settings.json>` (SP4 design §8.4; §9 outcome 5 — the flag is
8//! accepted, but the agent's own writes went to the default `.local/share/amp` and did not follow it).
9
10use std::path::PathBuf;
11
12use super::{
13    Adapter, AdapterEvidence, AdapterMetadata, Capability, CapabilityClaim, CapabilityState,
14    ConflictOption, Mechanism, PathKind, PlanContext, PlannedLaunch, SupportLevel, profile_dir,
15};
16use crate::config::AppRoot;
17use crate::error::Result;
18use crate::name::ProfileName;
19
20const FLAG: &str = "--settings-file";
21const FILE_NAME: &str = "settings.json";
22
23/// Every spelling of the option Amp launches with; `--settings-file` first, so it is the one reported.
24/// Only this spelling was measured — the probe's candidate sweep probed file contents, not flag
25/// spellings, so Q6 stays unanswered and no abbreviation may ride along unmeasured (tracked debt).
26const SETTINGS_OPTION: ConflictOption = ConflictOption { long: &[FLAG], short: None };
27
28/// A zero-byte `settings.json`: the smallest content the agent accepts (candidate sweep: the empty
29/// file and {} both exited 0) that sets no option, so it is what ships per §8.4 — a settings-file
30/// adapter needs the file to exist before `amp` reads it.
31pub const INITIAL_CONFIG: &[u8] = b"";
32
33/// Shown on every launch.
34pub const LAYERING_NOTE: &str = "--settings-file selects the user settings file, which workspace settings override and managed settings override both";
35
36static METADATA: AdapterMetadata = AdapterMetadata {
37    id: "amp",
38    executable: "amp",
39    mechanism: Mechanism::FlagFile(SETTINGS_OPTION),
40    support: SupportLevel::Proven,
41    evidence: AdapterEvidence {
42        mechanism_id: "amp-settings-file-v1",
43        verified_at: "2026-10-25",
44        upstream_version: "0.0.1792872041-gb83e0b",
45        source_url: "https://ampcode.com/docs/cli/settings",
46        notes: "measured in a sandbox at 0.0.1792872041-gb83e0b: a launch with --settings-file exits 0, \
47                but the agent's own writes did not follow it — `accounts.json` and `device-id.json` were \
48                written under the default `.local/share/amp` while the flag pointed at the probe's target \
49                (§9 outcome 5), and the default `.config/amp` was `(absent)` at baseline and stayed so; \
50                the candidate sweep accepted the empty file and {} (all exit 0); the strings scan found \
51                AMP_API_KEY, AMP_URL and AMP_SETTINGS_FILE documented, and `--settings-file` itself does \
52                not appear verbatim in the truncated help capture — the acceptance evidence is the \
53                exit-0 use of the flag",
54    },
55    capabilities: &[
56        CapabilityClaim {
57            capability: Capability::ConfigIsolation,
58            state: CapabilityState::NotGuaranteed,
59            basis: "measured: the flag was accepted (exit 0) but moved nothing to the target — \
60                    `+ f /home/agent/.local/share/amp/device-id.json` was written at the default, and \
61                    `.local/share/amp` was the only observed write target of the launch, while the \
62                    target's `settings.json` was the probe's own file and stayed the only file there",
63        },
64        CapabilityClaim {
65            capability: Capability::CredentialIsolation,
66            state: CapabilityState::Unknown,
67            basis: "unmeasured: no credential file was observed anywhere in the delta and no login step \
68                    ran, so where stored credentials live was not established; `AMP_API_KEY` \
69                    (strings: `documented AMP_API_KEY: present`) is the documented bypass variable",
70        },
71        CapabilityClaim {
72            capability: Capability::StateIsolation,
73            state: CapabilityState::NotGuaranteed,
74            basis: "measured: `+ f /home/agent/.local/share/amp/accounts.json` and \
75                    `+ f .../device-id.json` were written at the default while pointed at the flag's \
76                    target — the device identity and account store do not move with `--settings-file`",
77        },
78    ],
79    env: &[],
80    // Empty on purpose. `--settings-file` is the mechanism's own option, and `check_conflicts` scans
81    // that alongside this list, so naming it here again refused nothing extra. No abbreviated spelling
82    // was measured (Q6 unanswered), so there is no list behind it either.
83    conflicts: &[],
84    credential_env: &["AMP_API_KEY"],
85};
86
87/// The Amp adapter.
88#[derive(Debug, Clone, Copy)]
89pub struct Amp;
90
91impl Adapter for Amp {
92    fn metadata(&self) -> &'static AdapterMetadata {
93        &METADATA
94    }
95
96    fn paths(&self, root: &AppRoot, profile: &ProfileName) -> Vec<(PathBuf, PathKind)> {
97        let dir = profile_dir(root, profile, METADATA.id);
98        let file = dir.join(FILE_NAME);
99        vec![(dir, PathKind::Dir), (file, PathKind::File { contents: INITIAL_CONFIG })]
100    }
101
102    fn plan(&self, ctx: &PlanContext<'_>) -> Result<PlannedLaunch> {
103        let mut planned = METADATA.mechanism.plan(self, ctx)?;
104        planned.notes.push(LAYERING_NOTE.to_owned());
105        Ok(planned)
106    }
107}