fix(cli): surface agent_custom_instructions on add in both CLIs (#6910)
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
# Node CLI (`cli/node/`)
|
||||||
|
|
||||||
|
The `@mem0/cli` package on npm. Commander-based, entry point `mem0`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm run build # tsup (ESM)
|
||||||
|
pnpm run lint # biome check src/
|
||||||
|
pnpm run lint:fix # biome check --write src/
|
||||||
|
pnpm run typecheck # tsc --noEmit
|
||||||
|
pnpm run test # vitest run
|
||||||
|
pnpm run test:watch
|
||||||
|
pnpm run dev # tsx src/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
pnpm only. Never npm, never yarn.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
> **Biome, not ESLint. vitest, not jest.** `mem0-ts/` uses Prettier + jest and
|
||||||
|
> `integrations/vercel-ai-sdk/` uses ESLint + jest. Running those tools here produces
|
||||||
|
> spurious diffs. Every toolchain in this repo is per-package.
|
||||||
|
|
||||||
|
- **Node 18+** required.
|
||||||
|
- **Build:** tsup, ESM output only.
|
||||||
|
- **Linter and formatter:** Biome, configured in `biome.json`.
|
||||||
|
- **Tests:** vitest.
|
||||||
|
- **TypeScript strict mode.** ES module `import` syntax only, never `require()`.
|
||||||
|
|
||||||
|
Run `pnpm run typecheck` after every change.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Commander + Chalk + ora + cli-table3, and `mem0ai` (npm) for API calls.
|
||||||
|
|
||||||
|
## CI and release
|
||||||
|
|
||||||
|
- CI: `cli-node-ci.yml`, Biome + tsc + vitest + tsup build on Node 20 and 22.
|
||||||
|
- Release: tag prefix `cli-node-v*` dispatches `cli-node-cd.yml`, publishing to npm over OIDC.
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
AGENTS.md
|
||||||
@@ -15,6 +15,7 @@ export interface AddOptions {
|
|||||||
infer?: boolean;
|
infer?: boolean;
|
||||||
expires?: string;
|
expires?: string;
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
|
agentCustomInstructions?: string;
|
||||||
customCategories?: Record<string, string>[];
|
customCategories?: Record<string, string>[];
|
||||||
structuredDataSchema?: Record<string, unknown>;
|
structuredDataSchema?: Record<string, unknown>;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ export class PlatformBackend implements Backend {
|
|||||||
if (opts.expires) payload.expiration_date = opts.expires;
|
if (opts.expires) payload.expiration_date = opts.expires;
|
||||||
if (opts.customInstructions)
|
if (opts.customInstructions)
|
||||||
payload.custom_instructions = opts.customInstructions;
|
payload.custom_instructions = opts.customInstructions;
|
||||||
|
if (opts.agentCustomInstructions)
|
||||||
|
payload.agent_custom_instructions = opts.agentCustomInstructions;
|
||||||
if (opts.customCategories)
|
if (opts.customCategories)
|
||||||
payload.custom_categories = opts.customCategories;
|
payload.custom_categories = opts.customCategories;
|
||||||
if (opts.structuredDataSchema)
|
if (opts.structuredDataSchema)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export async function cmdAdd(
|
|||||||
expires?: string;
|
expires?: string;
|
||||||
categories?: string;
|
categories?: string;
|
||||||
customInstructions?: string;
|
customInstructions?: string;
|
||||||
|
agentCustomInstructions?: string;
|
||||||
customCategories?: string;
|
customCategories?: string;
|
||||||
structuredDataSchema?: string;
|
structuredDataSchema?: string;
|
||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
@@ -153,6 +154,7 @@ export async function cmdAdd(
|
|||||||
infer: opts.infer !== false,
|
infer: opts.infer !== false,
|
||||||
expires: opts.expires,
|
expires: opts.expires,
|
||||||
customInstructions: opts.customInstructions,
|
customInstructions: opts.customInstructions,
|
||||||
|
agentCustomInstructions: opts.agentCustomInstructions,
|
||||||
customCategories: customCats,
|
customCategories: customCats,
|
||||||
structuredDataSchema: schema,
|
structuredDataSchema: schema,
|
||||||
timestamp: opts.timestamp,
|
timestamp: opts.timestamp,
|
||||||
|
|||||||
@@ -328,6 +328,10 @@ program
|
|||||||
"--custom-instructions <text>",
|
"--custom-instructions <text>",
|
||||||
"Custom instructions for fact extraction.",
|
"Custom instructions for fact extraction.",
|
||||||
)
|
)
|
||||||
|
.option(
|
||||||
|
"--agent-custom-instructions <text>",
|
||||||
|
"Extraction instructions for agent-scoped memories, overriding the project setting.",
|
||||||
|
)
|
||||||
.option(
|
.option(
|
||||||
"--custom-categories <json>",
|
"--custom-categories <json>",
|
||||||
"Custom categories as a JSON array of {name: description} objects.",
|
"Custom categories as a JSON array of {name: description} objects.",
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const ADD_MAPPING: Record<string, string[]> = {
|
|||||||
metadata: ["--metadata"],
|
metadata: ["--metadata"],
|
||||||
expiration_date: ["--expires"],
|
expiration_date: ["--expires"],
|
||||||
custom_instructions: ["--custom-instructions"],
|
custom_instructions: ["--custom-instructions"],
|
||||||
|
agent_custom_instructions: ["--agent-custom-instructions"],
|
||||||
custom_categories: ["--custom-categories"],
|
custom_categories: ["--custom-categories"],
|
||||||
infer: ["--no-infer"],
|
infer: ["--no-infer"],
|
||||||
immutable: ["--immutable"],
|
immutable: ["--immutable"],
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => {
|
|||||||
metadata: { source: "test" },
|
metadata: { source: "test" },
|
||||||
expires: "2099-01-01",
|
expires: "2099-01-01",
|
||||||
customInstructions: "Extract only preferences.",
|
customInstructions: "Extract only preferences.",
|
||||||
|
agentCustomInstructions: "Extract only tool outcomes.",
|
||||||
customCategories: [{ prefs: "user preferences" }],
|
customCategories: [{ prefs: "user preferences" }],
|
||||||
structuredDataSchema: { type: "object" },
|
structuredDataSchema: { type: "object" },
|
||||||
timestamp: 1700000000,
|
timestamp: 1700000000,
|
||||||
@@ -91,6 +92,9 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => {
|
|||||||
|
|
||||||
const payload = spy.mock.calls[0][2].json;
|
const payload = spy.mock.calls[0][2].json;
|
||||||
expect(payload.custom_instructions).toBe("Extract only preferences.");
|
expect(payload.custom_instructions).toBe("Extract only preferences.");
|
||||||
|
expect(payload.agent_custom_instructions).toBe(
|
||||||
|
"Extract only tool outcomes.",
|
||||||
|
);
|
||||||
expect(payload.custom_categories).toEqual([{ prefs: "user preferences" }]);
|
expect(payload.custom_categories).toEqual([{ prefs: "user preferences" }]);
|
||||||
expect(payload.structured_data_schema).toEqual({ type: "object" });
|
expect(payload.structured_data_schema).toEqual({ type: "object" });
|
||||||
expect(payload.timestamp).toBe(1700000000);
|
expect(payload.timestamp).toBe(1700000000);
|
||||||
@@ -109,6 +113,7 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => {
|
|||||||
|
|
||||||
const payload = spy.mock.calls[0][2].json;
|
const payload = spy.mock.calls[0][2].json;
|
||||||
expect(payload).not.toHaveProperty("custom_instructions");
|
expect(payload).not.toHaveProperty("custom_instructions");
|
||||||
|
expect(payload).not.toHaveProperty("agent_custom_instructions");
|
||||||
expect(payload).not.toHaveProperty("custom_categories");
|
expect(payload).not.toHaveProperty("custom_categories");
|
||||||
expect(payload).not.toHaveProperty("structured_data_schema");
|
expect(payload).not.toHaveProperty("structured_data_schema");
|
||||||
expect(payload).not.toHaveProperty("timestamp");
|
expect(payload).not.toHaveProperty("timestamp");
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Python CLI (`cli/python/`)
|
||||||
|
|
||||||
|
The `mem0-cli` package on PyPI. Typer-based, entry point `mem0`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e ".[dev]" # dev install: ruff + pytest
|
||||||
|
ruff check . # lint
|
||||||
|
ruff format . # format
|
||||||
|
pytest # test
|
||||||
|
hatch build # build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
> **Line length is 100 here, not 120.** The root Python SDK uses 120. Running the root
|
||||||
|
> `make format` over this directory reformats every file and fails CI. Use the local
|
||||||
|
> `ruff` invocations above.
|
||||||
|
|
||||||
|
- **Python 3.10+.** Not 3.9, unlike the root SDK.
|
||||||
|
- **Ruff** with an extended rule set: `E`, `F`, `I`, `W`, `UP`, `B`, `SIM`, `RUF`.
|
||||||
|
Ignores `E501` (formatter handles it), `B008` (required by Typer's argument defaults),
|
||||||
|
and `SIM108`.
|
||||||
|
- **Ruff format:** double quotes, space indent, `docstring-code-format = true`.
|
||||||
|
- **isort** first-party is `mem0_cli` only.
|
||||||
|
- **pytest** for tests.
|
||||||
|
- Target version pinned to `py310`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
cli/python/
|
||||||
|
├── src/mem0_cli/ package source (src layout)
|
||||||
|
└── tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
Entry point: `mem0 = "mem0_cli.app:main"`.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Typer + Rich + httpx. `mem0ai` is **optional**, exposed through the `[oss]` extra for OSS mode. Do not promote it to a required dependency.
|
||||||
|
|
||||||
|
## CI and release
|
||||||
|
|
||||||
|
- CI: `cli-python-ci.yml`, ruff + pytest + `hatch build` on Python 3.10, 3.11, 3.12.
|
||||||
|
- Release: tag prefix `cli-v*` dispatches `cli-python-cd.yml`, publishing to PyPI over OIDC.
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
AGENTS.md
|
||||||
@@ -278,6 +278,11 @@ def add(
|
|||||||
custom_instructions: str | None = typer.Option(
|
custom_instructions: str | None = typer.Option(
|
||||||
None, "--custom-instructions", help="Custom instructions for fact extraction."
|
None, "--custom-instructions", help="Custom instructions for fact extraction."
|
||||||
),
|
),
|
||||||
|
agent_custom_instructions: str | None = typer.Option(
|
||||||
|
None,
|
||||||
|
"--agent-custom-instructions",
|
||||||
|
help="Extraction instructions for agent-scoped memories, overriding the project setting.",
|
||||||
|
),
|
||||||
custom_categories: str | None = typer.Option(
|
custom_categories: str | None = typer.Option(
|
||||||
None,
|
None,
|
||||||
"--custom-categories",
|
"--custom-categories",
|
||||||
@@ -327,6 +332,7 @@ def add(
|
|||||||
expires=expires,
|
expires=expires,
|
||||||
categories=categories,
|
categories=categories,
|
||||||
custom_instructions=custom_instructions,
|
custom_instructions=custom_instructions,
|
||||||
|
agent_custom_instructions=agent_custom_instructions,
|
||||||
custom_categories=custom_categories,
|
custom_categories=custom_categories,
|
||||||
structured_data_schema=structured_data_schema,
|
structured_data_schema=structured_data_schema,
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
@@ -1139,6 +1145,7 @@ def _build_help_json() -> dict:
|
|||||||
"--expires": "Expiration date (YYYY-MM-DD).",
|
"--expires": "Expiration date (YYYY-MM-DD).",
|
||||||
"--categories": "Not supported on add, use --custom-categories instead.",
|
"--categories": "Not supported on add, use --custom-categories instead.",
|
||||||
"--custom-instructions": "Custom instructions for fact extraction.",
|
"--custom-instructions": "Custom instructions for fact extraction.",
|
||||||
|
"--agent-custom-instructions": "Extraction instructions for agent-scoped memories, overriding the project setting.",
|
||||||
"--custom-categories": "Custom categories as a JSON array of {name: description} objects.",
|
"--custom-categories": "Custom categories as a JSON array of {name: description} objects.",
|
||||||
"--structured-data-schema": "Schema for structured data extraction, as JSON.",
|
"--structured-data-schema": "Schema for structured data extraction, as JSON.",
|
||||||
"--timestamp": "Unix timestamp for the memory.",
|
"--timestamp": "Unix timestamp for the memory.",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class Backend(ABC):
|
|||||||
infer: bool = True,
|
infer: bool = True,
|
||||||
expires: str | None = None,
|
expires: str | None = None,
|
||||||
custom_instructions: str | None = None,
|
custom_instructions: str | None = None,
|
||||||
|
agent_custom_instructions: str | None = None,
|
||||||
custom_categories: list[dict] | None = None,
|
custom_categories: list[dict] | None = None,
|
||||||
structured_data_schema: dict | None = None,
|
structured_data_schema: dict | None = None,
|
||||||
timestamp: int | None = None,
|
timestamp: int | None = None,
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ class PlatformBackend(Backend):
|
|||||||
infer: bool = True,
|
infer: bool = True,
|
||||||
expires: str | None = None,
|
expires: str | None = None,
|
||||||
custom_instructions: str | None = None,
|
custom_instructions: str | None = None,
|
||||||
|
agent_custom_instructions: str | None = None,
|
||||||
custom_categories: list[dict] | None = None,
|
custom_categories: list[dict] | None = None,
|
||||||
structured_data_schema: dict | None = None,
|
structured_data_schema: dict | None = None,
|
||||||
timestamp: int | None = None,
|
timestamp: int | None = None,
|
||||||
@@ -117,6 +118,8 @@ class PlatformBackend(Backend):
|
|||||||
payload["expiration_date"] = expires
|
payload["expiration_date"] = expires
|
||||||
if custom_instructions:
|
if custom_instructions:
|
||||||
payload["custom_instructions"] = custom_instructions
|
payload["custom_instructions"] = custom_instructions
|
||||||
|
if agent_custom_instructions:
|
||||||
|
payload["agent_custom_instructions"] = agent_custom_instructions
|
||||||
if custom_categories:
|
if custom_categories:
|
||||||
payload["custom_categories"] = custom_categories
|
payload["custom_categories"] = custom_categories
|
||||||
if structured_data_schema:
|
if structured_data_schema:
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ def cmd_add(
|
|||||||
expires: str | None,
|
expires: str | None,
|
||||||
categories: str | None,
|
categories: str | None,
|
||||||
custom_instructions: str | None = None,
|
custom_instructions: str | None = None,
|
||||||
|
agent_custom_instructions: str | None = None,
|
||||||
custom_categories: str | None = None,
|
custom_categories: str | None = None,
|
||||||
structured_data_schema: str | None = None,
|
structured_data_schema: str | None = None,
|
||||||
timestamp: int | None = None,
|
timestamp: int | None = None,
|
||||||
@@ -166,6 +167,7 @@ def cmd_add(
|
|||||||
infer=not no_infer,
|
infer=not no_infer,
|
||||||
expires=expires,
|
expires=expires,
|
||||||
custom_instructions=custom_instructions,
|
custom_instructions=custom_instructions,
|
||||||
|
agent_custom_instructions=agent_custom_instructions,
|
||||||
custom_categories=custom_cats,
|
custom_categories=custom_cats,
|
||||||
structured_data_schema=schema,
|
structured_data_schema=schema,
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ ADD_MAPPING: dict[str, list[str]] = {
|
|||||||
"metadata": ["metadata"],
|
"metadata": ["metadata"],
|
||||||
"expiration_date": ["expires"],
|
"expiration_date": ["expires"],
|
||||||
"custom_instructions": ["custom_instructions"],
|
"custom_instructions": ["custom_instructions"],
|
||||||
|
"agent_custom_instructions": ["agent_custom_instructions"],
|
||||||
"custom_categories": ["custom_categories"],
|
"custom_categories": ["custom_categories"],
|
||||||
"infer": ["no_infer"],
|
"infer": ["no_infer"],
|
||||||
"immutable": ["immutable"],
|
"immutable": ["immutable"],
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ class TestAddOptions:
|
|||||||
metadata={"source": "test"},
|
metadata={"source": "test"},
|
||||||
expires="2099-01-01",
|
expires="2099-01-01",
|
||||||
custom_instructions="Extract only preferences.",
|
custom_instructions="Extract only preferences.",
|
||||||
|
agent_custom_instructions="Extract only tool outcomes.",
|
||||||
custom_categories=[{"prefs": "user preferences"}],
|
custom_categories=[{"prefs": "user preferences"}],
|
||||||
structured_data_schema={"type": "object"},
|
structured_data_schema={"type": "object"},
|
||||||
timestamp=1700000000,
|
timestamp=1700000000,
|
||||||
)
|
)
|
||||||
payload = mock_request.call_args.kwargs["json"]
|
payload = mock_request.call_args.kwargs["json"]
|
||||||
assert payload["custom_instructions"] == "Extract only preferences."
|
assert payload["custom_instructions"] == "Extract only preferences."
|
||||||
|
assert payload["agent_custom_instructions"] == "Extract only tool outcomes."
|
||||||
assert payload["custom_categories"] == [{"prefs": "user preferences"}]
|
assert payload["custom_categories"] == [{"prefs": "user preferences"}]
|
||||||
assert payload["structured_data_schema"] == {"type": "object"}
|
assert payload["structured_data_schema"] == {"type": "object"}
|
||||||
assert payload["timestamp"] == 1700000000
|
assert payload["timestamp"] == 1700000000
|
||||||
@@ -40,6 +42,7 @@ class TestAddOptions:
|
|||||||
backend.add(content="hello", user_id="alice")
|
backend.add(content="hello", user_id="alice")
|
||||||
payload = mock_request.call_args.kwargs["json"]
|
payload = mock_request.call_args.kwargs["json"]
|
||||||
assert "custom_instructions" not in payload
|
assert "custom_instructions" not in payload
|
||||||
|
assert "agent_custom_instructions" not in payload
|
||||||
assert "custom_categories" not in payload
|
assert "custom_categories" not in payload
|
||||||
assert "structured_data_schema" not in payload
|
assert "structured_data_schema" not in payload
|
||||||
assert "timestamp" not in payload
|
assert "timestamp" not in payload
|
||||||
|
|||||||
Reference in New Issue
Block a user