Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .factory-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "agentmemory",
"owner": {
"name": "Rohit Ghumare",
"github": "rohitg00"
},
"plugins": [
{
"name": "agentmemory",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. Factory Droids integration.",
"source": "./plugin"
}
Comment on lines +1 to +12
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e032ad8: added marketplace coverage for .factory-plugin/marketplace.json, including presence, plugin name, and source path ./plugin.

]
}
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ PRs with commits lacking sign-off will not merge.
| `src/health/` | Liveness + readiness + alert thresholds. |
| `src/state/` | KV schema, keyed mutex, access log. |
| `integrations/` | First-party plugins: `hermes/`, `openclaw/`, `filesystem-watcher/`. |
| `plugin/` | Claude Code plugin (`agentmemory@agentmemory`). |
| `plugin/` | Claude Code / Codex CLI / Factory Droids plugin (`agentmemory@agentmemory`). |
| `website/` | Marketing site (Next.js 16). |
| `test/` | Vitest test suite. |

Expand Down
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Your coding agent remembers everything. No more re-explaining.
Built on <a href="https://github.com/iii-hq/iii">iii engine</a>
</strong><br/>
Persistent memory for Claude Code, Cursor, Gemini CLI, Codex CLI, Hermes, OpenClaw, pi, OpenCode, and any MCP client.
Persistent memory for Claude Code, Cursor, Gemini CLI, Codex CLI, Factory Droids, Hermes, OpenClaw, pi, OpenCode, and any MCP client.
</p>

<p align="center">
Expand Down Expand Up @@ -152,15 +152,15 @@ agentmemory works with any agent that supports hooks, MCP, or REST API. All agen
<sub>AgentSDKProvider</sub>
</td>
<td align="center" width="12.5%">
<img src="https://img.shields.io/badge/104-endpoints-1f6feb?style=flat-square" alt="REST API" width="48" /><br/>
<strong>Any agent</strong><br/>
<sub>REST API</sub>
<a href="https://factory.ai"><img src="https://github.com/factory.png?size=120" alt="Factory Droids" width="48" height="48" /></a><br/>
<strong>Factory Droids</strong><br/>
<sub>12 hooks + MCP + skills</sub>
</td>
</tr>
</table>

<p align="center">
<sub>Works with <strong>any</strong> agent that speaks MCP or HTTP. One server, memories shared across all of them.</sub>
<sub>Works with <strong>any</strong> agent that speaks MCP or HTTP (including REST-only agents like Aider). One server, memories shared across all of them.</sub>
</p>

---
Expand Down Expand Up @@ -374,6 +374,24 @@ The Codex plugin ships from the same `plugin/` directory as the Claude Code plug

Codex's hook engine injects `CLAUDE_PLUGIN_ROOT` into hook subprocesses (per [`codex-rs/hooks/src/engine/discovery.rs`](https://github.com/openai/codex/blob/main/codex-rs/hooks/src/engine/discovery.rs)), so the same hook scripts work across both hosts without duplication. Subagent / SessionEnd / Notification / TaskCompleted / PostToolUseFailure events are Claude-Code-only and are not registered for Codex.

### Factory Droids (Factory plugin marketplace)

```bash
# 1. start the memory server in a separate terminal
npx @agentmemory/agentmemory

# 2. install the plugin via the Factory marketplace
droid plugin install agentmemory
```

The Factory plugin ships from the `plugin/.factory-plugin/` directory. It registers:

- `@agentmemory/mcp` as an MCP server (all 51 tools via `plugin/.mcp.json`)
- 12 lifecycle hooks: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PreCompact`, `SubagentStart`, `SubagentStop`, `Notification`, `TaskCompleted`, `Stop`, `SessionEnd`
- 4 skills: `/recall`, `/remember`, `/session-history`, `/forget`

Factory's hook engine injects `FACTORY_PLUGIN_ROOT` into hook subprocesses, so the same `plugin/scripts/` hook scripts work across Claude Code, Codex, and Factory Droids without duplication.

<details>
<summary><b>OpenClaw (paste this prompt)</b></summary>

Expand Down Expand Up @@ -448,6 +466,7 @@ The agentmemory entry is the **same MCP server block** across every host that us
| **OpenClaw** | OpenClaw MCP config | Same `mcpServers` block, or use the deeper [memory plugin](integrations/openclaw/). |
| **Codex CLI (MCP only)** | `.codex/config.toml` | TOML shape: `codex mcp add agentmemory -- npx -y @agentmemory/mcp`, or add `[mcp_servers.agentmemory]` manually. |
| **Codex CLI (full plugin)** | Codex plugin marketplace | `codex plugin marketplace add rohitg00/agentmemory` then `codex plugin install agentmemory`. Registers MCP + 6 lifecycle hooks (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, Stop) + 4 skills. |
| **Factory Droids** | Factory plugin marketplace | `droid plugin install agentmemory`. Registers MCP + 12 lifecycle hooks (SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PreCompact, SubagentStart, SubagentStop, Notification, TaskCompleted, Stop, SessionEnd) + 4 skills via `plugin/.factory-plugin/`. |
| **OpenCode** | `opencode.json` | Different shape — top-level `mcp` key, command as array: `{"mcp": {"agentmemory": {"type": "local", "command": ["npx", "-y", "@agentmemory/mcp"], "enabled": true}}}`. |
| **pi** | `~/.pi/agent/extensions/agentmemory` | Copy [`integrations/pi`](integrations/pi/) and restart pi. |
| **Hermes Agent** | `~/.hermes/config.yaml` | Use the deeper [memory provider plugin](integrations/hermes/) with `memory.provider: agentmemory`. |
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ In scope:
- The `@agentmemory/mcp` standalone MCP server.
- The `@agentmemory/fs-watcher` connector.
- First-party integrations under `integrations/` (`hermes/`, `openclaw/`, `filesystem-watcher/`).
- The Claude Code plugin under `plugin/`.
- Plugins under `plugin/` (Claude Code, Codex CLI, Factory Droids).

Out of scope:

Expand Down
17 changes: 17 additions & 0 deletions plugin/.factory-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "agentmemory",
"version": "0.9.11",
"description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 51 MCP tools, 4 skills, real-time viewer.",
"author": {
Comment on lines +1 to +5
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e032ad8: added test/factory-plugin.test.ts to verify plugin/.factory-plugin/plugin.json exists, matches the root package.json version, and resolves its skills, mcpServers, and hooks paths.

"name": "Rohit Ghumare",
"url": "https://github.com/rohitg00"
},
"license": "Apache-2.0",
"homepage": "https://github.com/rohitg00/agentmemory",
"repository": "https://github.com/rohitg00/agentmemory",
"factoryVersion": ">=0.1.0",
"tags": ["memory", "persistence", "mcp", "knowledge-graph"],
"skills": "../skills/",
"mcpServers": "../.mcp.json",
"hooks": "../hooks/hooks.factory.json"
}
127 changes: 127 additions & 0 deletions plugin/hooks/hooks.factory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"hooks": [
{
"name": "session-start",
"event": "SessionStart",
"description": "Initialize session and inject context",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/session-start.mjs\""
}
},
{
"name": "user-prompt-submit",
Comment on lines +8 to +14
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e032ad8: the Factory plugin tests now validate every hooks.factory.json command uses the expected ${FACTORY_PLUGIN_ROOT}/scripts/*.mjs pattern and that each referenced script exists on disk.

"event": "UserPromptSubmit",
"description": "Capture user prompts",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/prompt-submit.mjs\""
}
},
{
"name": "pre-tool-use",
"event": "PreToolUse",
"description": "Capture tool intent",
"enabled": true,
"conditions": {
"toolNames": ["Edit", "Write", "Read", "Glob", "Grep", "bash", "run_command", "edit_file", "write_file"]
},
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/pre-tool-use.mjs\""
}
},
{
"name": "post-tool-use",
"event": "PostToolUse",
"description": "Capture tool output",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/post-tool-use.mjs\""
}
},
{
"name": "post-tool-use-failure",
"event": "PostToolUseFailure",
"description": "Capture tool errors",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/post-tool-failure.mjs\""
}
},
{
"name": "pre-compact",
"event": "PreCompact",
"description": "Hook before memory compaction",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/pre-compact.mjs\""
}
},
{
"name": "subagent-start",
"event": "SubagentStart",
"description": "Capture subagent execution",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/subagent-start.mjs\""
}
},
{
"name": "subagent-stop",
"event": "SubagentStop",
"description": "Capture subagent completion",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/subagent-stop.mjs\""
}
},
{
"name": "notification",
"event": "Notification",
"description": "Capture notifications",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/notification.mjs\""
}
},
{
"name": "task-completed",
"event": "TaskCompleted",
"description": "Capture task completion",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/task-completed.mjs\""
}
},
{
"name": "stop",
"event": "Stop",
"description": "Capture stop events",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/stop.mjs\""
}
},
{
"name": "session-end",
"event": "SessionEnd",
"description": "Consolidate memory at end of session",
"enabled": true,
"action": {
"type": "command",
"command": "node \"${FACTORY_PLUGIN_ROOT}/scripts/session-end.mjs\""
}
}
]
}
110 changes: 110 additions & 0 deletions test/factory-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { describe, expect, it } from "vitest";
import { existsSync, readFileSync } from "node:fs";
import { join, resolve } from "node:path";

const repoRoot = resolve(__dirname, "..");
const pluginRoot = join(repoRoot, "plugin");
const factoryPluginRoot = join(pluginRoot, ".factory-plugin");

function readJson<T = unknown>(path: string): T {
return JSON.parse(readFileSync(path, "utf-8")) as T;
}

describe("Factory Droids plugin manifest", () => {
it("ships .factory-plugin/plugin.json with name + version + references", () => {
const manifestPath = join(factoryPluginRoot, "plugin.json");
expect(existsSync(manifestPath)).toBe(true);
const manifest = readJson<{
name: string;
version: string;
description?: string;
factoryVersion?: string;
skills?: string;
mcpServers?: string;
hooks?: string;
}>(manifestPath);
expect(manifest.name).toBe("agentmemory");
expect(manifest.name).toMatch(/^[a-z][a-z0-9-]*$/);
expect(manifest.version).toMatch(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/);
expect(manifest.factoryVersion).toBeDefined();
expect(manifest.skills).toBeDefined();
expect(manifest.mcpServers).toBeDefined();
expect(manifest.hooks).toBeDefined();
});

it("manifest version matches main package.json", () => {
const pkgVer = readJson<{ version: string }>(join(repoRoot, "package.json")).version;
const factoryVer = readJson<{ version: string }>(
join(factoryPluginRoot, "plugin.json"),
).version;
expect(factoryVer).toBe(pkgVer);
});

it("all referenced manifest paths resolve to existing files / directories", () => {
const manifest = readJson<{ skills: string; mcpServers: string; hooks: string }>(
join(factoryPluginRoot, "plugin.json"),
);
expect(existsSync(join(factoryPluginRoot, manifest.skills))).toBe(true);
expect(existsSync(join(factoryPluginRoot, manifest.mcpServers))).toBe(true);
expect(existsSync(join(factoryPluginRoot, manifest.hooks))).toBe(true);
});

it("hooks.factory.json registers the same first-class lifecycle events as Claude Code", () => {
const hooks = readJson<{ hooks: Array<{ event: string }> }>(
join(pluginRoot, "hooks/hooks.factory.json"),
);
const events = hooks.hooks.map((hook) => hook.event).sort();
expect(events).toEqual([
"Notification",
"PostToolUse",
"PostToolUseFailure",
"PreCompact",
"PreToolUse",
"SessionEnd",
"SessionStart",
"Stop",
"SubagentStart",
"SubagentStop",
"TaskCompleted",
"UserPromptSubmit",
]);
});

it("hook command scripts referenced in hooks.factory.json exist on disk", () => {
const hooks = readJson<{
hooks: Array<{ action: { type: string; command: string } }>;
}>(join(pluginRoot, "hooks/hooks.factory.json"));
const scriptRefs = new Set<string>();
for (const hook of hooks.hooks) {
expect(hook.action.type).toBe("command");
const match = hook.action.command.match(
/^node "\$\{FACTORY_PLUGIN_ROOT\}\/(scripts\/[^"\s]+\.mjs)"$/,
);
expect(match, `invalid Factory hook command: ${hook.action.command}`).not.toBeNull();
if (match) scriptRefs.add(match[1]);
}
expect(scriptRefs.size).toBeGreaterThan(0);
for (const rel of scriptRefs) {
expect(existsSync(join(pluginRoot, rel)), `missing hook script: ${rel}`).toBe(true);
}
});
});

describe("Factory marketplace.json (.factory-plugin/marketplace.json at repo root)", () => {
it("ships a marketplace manifest pointing at the plugin/ subdirectory", () => {
const marketplacePath = join(repoRoot, ".factory-plugin/marketplace.json");
expect(existsSync(marketplacePath)).toBe(true);
const marketplace = readJson<{
name: string;
plugins: Array<{
name: string;
source: string;
}>;
}>(marketplacePath);
expect(marketplace.name).toBe("agentmemory");
expect(marketplace.plugins).toHaveLength(1);
const entry = marketplace.plugins[0];
expect(entry.name).toBe("agentmemory");
expect(entry.source).toBe("./plugin");
});
});