diff --git a/.factory-plugin/marketplace.json b/.factory-plugin/marketplace.json
new file mode 100644
index 0000000..1805c2a
--- /dev/null
+++ b/.factory-plugin/marketplace.json
@@ -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"
+ }
+ ]
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 39d38d6..400bd4e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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. |
diff --git a/README.md b/README.md
index 4f330c5..195d8dd 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
Your coding agent remembers everything. No more re-explaining.
Built on iii engine
- 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.
@@ -152,15 +152,15 @@ agentmemory works with any agent that supports hooks, MCP, or REST API. All agen
AgentSDKProvider
-
-Any agent
-REST API
+
+Factory Droids
+12 hooks + MCP + skills
|
- Works with any agent that speaks MCP or HTTP. One server, memories shared across all of them.
+ Works with any agent that speaks MCP or HTTP (including REST-only agents like Aider). One server, memories shared across all of them.
---
@@ -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.
+
OpenClaw (paste this prompt)
@@ -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`. |
diff --git a/SECURITY.md b/SECURITY.md
index f5980ec..119977f 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -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:
diff --git a/plugin/.factory-plugin/plugin.json b/plugin/.factory-plugin/plugin.json
new file mode 100644
index 0000000..365d941
--- /dev/null
+++ b/plugin/.factory-plugin/plugin.json
@@ -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": {
+ "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"
+}
diff --git a/plugin/hooks/hooks.factory.json b/plugin/hooks/hooks.factory.json
new file mode 100644
index 0000000..941bd20
--- /dev/null
+++ b/plugin/hooks/hooks.factory.json
@@ -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",
+ "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\""
+ }
+ }
+ ]
+}
diff --git a/test/factory-plugin.test.ts b/test/factory-plugin.test.ts
new file mode 100644
index 0000000..fb1b303
--- /dev/null
+++ b/test/factory-plugin.test.ts
@@ -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(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();
+ 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");
+ });
+});