Skip to main content

sqlite_graphrag/commands/remember/
args.rs

1//! CLI arguments for the `remember` command.
2
3use crate::cli::MemoryType;
4use crate::output::JsonOutputFormat;
5
6#[derive(clap::Args)]
7/// Remember args.
8///
9/// GAP-SG-216: this struct carried an `after_long_help` block from v1.0.x until
10/// v1.2.8 and NONE of it ever reached a terminal. `Commands::Remember` in
11/// `crate::cli::commands` declares the same attribute on the enum variant, and
12/// clap keeps that one — silently, with no warning and no lint. The dead block
13/// is where the false claim "reference, skill, document, note, user, feedback
14/// are MEMORY types only — NOT valid for entities" survived two minor releases
15/// after v1.1.8 made the parser accept all six.
16///
17/// The authoritative block now lives beside the variant, where it renders, and
18/// `tests/remember_input_contract_gate.rs` reads THAT text. Do not add a second
19/// `after_long_help` here: a help attribute nothing renders is prose that can
20/// only rot.
21pub struct RememberArgs {
22    /// GAP-SG-216: memory name as a positional argument, the same form `edit`,
23    /// `read`, `forget`, `history`, `related`, `rename`, `restore` and
24    /// `memory-entities` already accept. `remember` alone refused it, and the
25    /// refusal arrived as clap's generic `unexpected argument`, which names
26    /// neither `--name` nor the reason.
27    #[arg(
28        value_name = "NAME",
29        conflicts_with = "name",
30        help = "Memory name in kebab-case; alternative to --name"
31    )]
32    pub name_positional: Option<String>,
33    /// Memory name in kebab-case (lowercase letters, digits, hyphens).
34    /// Acts as unique key within the namespace; collisions trigger merge or rejection.
35    ///
36    /// Optional at the clap layer only because the positional form is the
37    /// alternative; `super::name::resolve` refuses when NEITHER is given.
38    #[arg(long)]
39    pub name: Option<String>,
40    #[arg(
41        long,
42        value_enum,
43        long_help = "Memory kind stored in `memories.type`. Required when creating a new memory. Optional with --force-merge: if omitted the existing memory type is inherited. This is NOT the graph `entity_type` used in `--entities-file`. Valid values: user, feedback, project, reference, decision, incident, skill, document, note."
44    )]
45    /// Item.
46    pub r#type: Option<MemoryType>,
47    /// Short description (≤500 chars) summarizing the memory for use in `list` and `recall` snippets.
48    /// Required when creating a new memory. Optional with --force-merge: if omitted the existing description is inherited.
49    ///
50    /// GAP-SG-33: `allow_hyphen_values` lets a description that begins with a
51    /// hyphen (e.g. `"- bullet"`) be accepted as a value instead of being
52    /// mistaken for a flag.
53    #[arg(long, allow_hyphen_values = true)]
54    pub description: Option<String>,
55    /// Inline body content. Mutually exclusive with --body-file, --body-stdin, --graph-stdin.
56    /// Maximum 512000 bytes; rejected if empty without an external graph.
57    ///
58    /// GAP-SG-33: `allow_hyphen_values` lets a body that begins with a hyphen
59    /// (e.g. a markdown bullet list) be accepted as a value.
60    #[arg(
61        long,
62        allow_hyphen_values = true,
63        help = "Inline body content (max 500 KB / 512000 bytes and 30000 estimated tokens; split dense bodies into multiple memories at ~25000 tokens, or use --body-file)",
64        conflicts_with_all = ["body_file", "body_stdin", "graph_stdin"]
65    )]
66    pub body: Option<String>,
67    #[arg(
68        long,
69        help = "Read body from a file instead of --body",
70        conflicts_with_all = ["body", "body_stdin", "graph_stdin"]
71    )]
72    /// Body file.
73    pub body_file: Option<std::path::PathBuf>,
74    /// Read body from stdin until EOF. Useful in pipes (echo "..." | sqlite-graphrag remember ...).
75    /// Mutually exclusive with --body, --body-file, --graph-stdin.
76    #[arg(
77        long,
78        conflicts_with_all = ["body", "body_file", "graph_stdin"]
79    )]
80    pub body_stdin: bool,
81    #[arg(
82        long,
83        help = "JSON file containing entities to associate with this memory"
84    )]
85    /// Entities file.
86    pub entities_file: Option<std::path::PathBuf>,
87    #[arg(
88        long,
89        help = "JSON file containing relationships to associate with this memory"
90    )]
91    /// Relationships file.
92    pub relationships_file: Option<std::path::PathBuf>,
93    #[arg(
94        long,
95        help = "Read graph JSON (body + entities + relationships) from stdin",
96        conflicts_with_all = [
97            "body",
98            "body_file",
99            "body_stdin",
100            "entities_file",
101            "relationships_file",
102            "graph_file"
103        ]
104    )]
105    /// Graph stdin.
106    pub graph_stdin: bool,
107    /// GAP-SG-30: read graph JSON (`{body, entities, relationships}`) from a
108    /// FILE instead of stdin, so a curated graph can combine with a body
109    /// supplied via --body / --body-file / --body-stdin (which previously
110    /// conflicted with --graph-stdin over the single stdin). The file's `body`
111    /// field is used only when no other body source is given; otherwise the
112    /// body source wins and only the file's entities/relationships are applied.
113    #[arg(
114        long,
115        value_name = "PATH",
116        help = "Read graph JSON (body + entities + relationships) from a file (combines with --body/--body-file/--body-stdin)",
117        conflicts_with_all = ["graph_stdin", "entities_file", "relationships_file"]
118    )]
119    pub graph_file: Option<std::path::PathBuf>,
120    #[arg(long, help = "Namespace (flag / XDG namespace.default / global)")]
121    /// Namespace scope.
122    pub namespace: Option<String>,
123    /// Inline JSON object with arbitrary metadata key-value pairs. Mutually exclusive with --metadata-file.
124    #[arg(long)]
125    pub metadata: Option<String>,
126    /// Metadata file.
127    #[arg(long, help = "JSON file containing metadata key-value pairs")]
128    pub metadata_file: Option<std::path::PathBuf>,
129    /// Force merge.
130    #[arg(long)]
131    pub force_merge: bool,
132    #[arg(
133        long,
134        value_name = "EPOCH_OR_RFC3339",
135        value_parser = crate::parsers::parse_expected_updated_at,
136        long_help = "Optimistic lock: reject if updated_at does not match. \
137Accepts Unix epoch (e.g. 1700000000) or RFC 3339 (e.g. 2026-04-19T12:00:00Z)."
138    )]
139    /// Expected updated at.
140    pub expected_updated_at: Option<i64>,
141    #[arg(
142        long,
143                value_parser = crate::parsers::parse_bool_flexible,
144        action = clap::ArgAction::Set,
145        num_args = 0..=1,
146        default_missing_value = "true",
147        default_value = "false",
148        help = "Enable automatic URL-regex extraction from body (URL-regex only since v1.0.79)"
149    )]
150    /// Enable NER.
151    pub enable_ner: bool,
152    /// Skip extraction.
153    #[arg(long, hide = true)]
154    pub skip_extraction: bool,
155    /// Explicitly clear the body content (set to empty string). Required to distinguish
156    /// intentional body clearing from accidental omission during --force-merge.
157    /// Without this flag, an empty body passed to --force-merge preserves the existing body.
158    #[arg(
159        long,
160        default_value_t = false,
161        help = "Explicitly clear body content during --force-merge (without this flag, an empty body is ignored and the existing body is kept)"
162    )]
163    pub clear_body: bool,
164    /// Validate input and report planned actions without persisting.
165    #[arg(
166        long,
167        default_value_t = false,
168        help = "Validate input and report planned actions without persisting"
169    )]
170    pub dry_run: bool,
171    /// GAP-SG-37: reject (instead of silently normalizing) when the supplied
172    /// --name is not already canonical kebab-case. Use this when the literal
173    /// name matters and a silent transform would surprise downstream lookups.
174    #[arg(
175        long,
176        default_value_t = false,
177        help = "Reject the write if --name would be normalized to kebab-case (preserve-name guard)"
178    )]
179    pub strict_name: bool,
180    /// GAP-SG-216: reject a declared `entity_type` outside the thirteen
181    /// canonical kinds.
182    ///
183    /// The sibling of [`Self::strict_name`]: one field guards the name the
184    /// caller typed, the other the taxonomy. An open vocabulary stays the
185    /// default because LLM extraction depends on it — an extractor cannot be
186    /// asked to emit only thirteen labels, while a caller who typed one can opt
187    /// into being refused.
188    #[arg(
189        long,
190        default_value_t = false,
191        help = "Reject the write if a declared entity_type is outside the canonical vocabulary"
192    )]
193    pub strict_entity_types: bool,
194    /// GAP-SG-51: with --force-merge, REPLACE the memory's entity/relationship
195    /// bindings with the supplied set instead of merging additively. Combined
196    /// with an empty `entities`/`relationships` payload this clears all bindings
197    /// without deleting the memory.
198    #[arg(
199        long,
200        default_value_t = false,
201        help = "With --force-merge, replace (not merge) the memory's graph bindings; empty entities clears them"
202    )]
203    pub replace_graph: bool,
204    /// Optional opaque session identifier for tracing memory provenance across multi-agent runs.
205    #[arg(long)]
206    pub session_id: Option<String>,
207    /// Output format.
208    #[arg(long, value_enum, default_value_t = JsonOutputFormat::Json)]
209    pub format: JsonOutputFormat,
210    /// Emit machine-readable JSON on stdout.
211    #[arg(long, hide = true, help = "No-op; JSON is always emitted on stdout")]
212    pub json: bool,
213    /// Path to the SQLite database file.
214    #[arg(long)]
215    pub db: Option<String>,
216    /// Maximum process RSS in MiB; abort if exceeded during embedding.
217    #[arg(long, default_value_t = crate::constants::DEFAULT_MAX_RSS_MB,
218          help = "Maximum process RSS in MiB; abort if exceeded during embedding (default: 8192)")]
219    pub max_rss_mb: u64,
220    /// G42/S3 (v1.0.79): maximum simultaneous LLM embedding subprocesses.
221    /// The effective value is further bounded by CPU count and available
222    /// RAM (permits = min(N, cpus, ram_livre*0.5/350MB), clamp [1, 32]).
223    #[arg(long, default_value_t = 4, value_name = "N",
224          value_parser = clap::value_parser!(u64).range(1..=32),
225          help = "Maximum simultaneous LLM embedding subprocesses (default: 4, clamp [1,32])")]
226    pub llm_parallelism: u64,
227    /// GAP-CLI-PRIO-02: after write, enqueue entity-descriptions for the
228    /// entities linked in this call (priority hot set). Default false —
229    /// operators enable when they want automatic priority enrich.
230    #[arg(long, default_value_t = false)]
231    pub enqueue_enrich: bool,
232}