Skip to content
Merged
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
13 changes: 9 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1058,10 +1058,15 @@ The default agent uses `shared` policy for backward compatibility — existing
installs see all their memories unchanged.

**Identity inheritance** — each agent can have its own identity files under
`$SIGNET_WORKSPACE/agents/{name}/`. Only `SOUL.md` and `IDENTITY.md` are
expected to be overridden; all other files (`AGENTS.md`, `USER.md`, etc.)
inherit from the workspace root. The daemon's file watcher monitors
`$SIGNET_WORKSPACE/agents/` and triggers a harness sync on change.
`$SIGNET_WORKSPACE/agents/{name}/`. On session start, the daemon checks the
agent directory first for the standard identity files (`AGENTS.md`, `SOUL.md`,
`IDENTITY.md`, `USER.md`, `TOOLS.md`, `HEARTBEAT.md`, `MEMORY.md`,
`BOOTSTRAP.md`) and falls back to the workspace root when an agent-local file
does not exist. This lets named agents override prompt identity and working
memory without copying the whole workspace. If no agent-local `MEMORY.md`
exists, the shared root `MEMORY.md` remains the working-memory projection for
that agent. The daemon's file watcher monitors `$SIGNET_WORKSPACE/agents/`
and triggers a harness sync on change.

**OpenClaw session keys** — OpenClaw encodes the agent ID in session keys as
`agent:{id}:{rest}`. The daemon's `resolveAgentId()` helper auto-parses this
Expand Down
8 changes: 6 additions & 2 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Options:
| `--name <name>` | Agent name in non-interactive mode |
| `--description <description>` | Agent description in non-interactive mode |
| `--deployment-type <type>` | Deployment context (`local`, `vps`, `server`) used for interactive guidance and non-interactive inferred defaults |
| `--harness <harness>` | Repeatable/comma-separated harness list (`claude-code`, `opencode`, `openclaw`, `oh-my-pi`, `pi`, `codex`, `forge`) |
| `--harness <harness>` | Repeatable/comma-separated harness list (`claude-code`, `opencode`, `openclaw`, `hermes-agent`, `oh-my-pi`, `pi`, `codex`, `forge`) |
| `--embedding-provider <provider>` | Non-interactive embedding provider (`ollama`, `openai`, `native`, `none`) |
| `--embedding-model <model>` | Non-interactive embedding model |
| `--extraction-provider <provider>` | Non-interactive extraction provider (`claude-code`, `codex`, `ollama`, `opencode`, `openrouter`, `none`) |
Expand Down Expand Up @@ -173,9 +173,13 @@ Wizard steps:
1. **Agent Name** - What to call your agent
2. **Harnesses** - Which AI platforms you use:
- Claude Code (Anthropic CLI)
- Codex
- OpenCode
- OpenClaw
- Codex
- Oh My Pi
- Pi
- Hermes Agent
- Forge
3. **OpenClaw Workspace** - Appears only when an existing OpenClaw config
is detected; workspace is patched only if you opt in, and setup warns
that uninstalling OpenClaw can delete this workspace unless backups exist
Expand Down
13 changes: 9 additions & 4 deletions docs/HARNESSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,9 +683,13 @@ Hermes lifecycle.

| Tool | Description |
|------|-------------|
| `signet_search` | Hybrid memory search (keyword + semantic + knowledge graph) |
| `signet_store` | Store a fact/preference/decision with auto entity extraction |
| `signet_profile` | Broad overview of stored memories and working context |
| `memory_search` | Hybrid memory search (keyword + semantic + knowledge graph) |
| `memory_store` | Store a fact/preference/decision with auto entity extraction |
| `memory_get` | Retrieve a memory by ID |
| `memory_list` | List memories with optional filters |
| `memory_modify` | Edit an existing memory |
| `memory_forget` | Soft-delete a memory |
| `recall` / `remember` | Compatibility aliases for search/store |

### Supported hooks

Expand Down Expand Up @@ -738,7 +742,8 @@ All harnesses target the same model:

- one agent
- many sessions / branches
- one shared `MEMORY.md` head
- one shared root `MEMORY.md` head, with optional agent-local `MEMORY.md`
overrides for named agents
- structured retrieval first
- transcripts as fallback / deep history
- compaction artifacts feeding the same temporal DAG
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function registerAppCommands(program: Command, deps: AppDeps): void {
.option("--network-mode <mode>", "Daemon network mode in non-interactive mode (localhost, tailscale)")
.option(
"--harness <harness>",
"Harness to configure (repeatable or comma-separated: claude-code, codex, opencode, openclaw, oh-my-pi, forge)",
"Harness to configure (repeatable or comma-separated: claude-code, codex, opencode, openclaw, oh-my-pi, pi, hermes-agent, forge)",
deps.collectListOption,
[],
)
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/commands/memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ describe("registerMemoryCommands recall", () => {
};

let capturedBody: unknown;
let capturedTimeout: number | undefined;
const program = new Command();
registerMemoryCommands(program, {
ensureDaemonForSecrets: async () => true,
secretApiCall: async (_method, _path, body) => {
secretApiCall: async (_method, _path, body, timeoutMs) => {
capturedBody = body;
capturedTimeout = timeoutMs;
return {
ok: true,
data: {
Expand Down Expand Up @@ -113,6 +115,7 @@ describe("registerMemoryCommands recall", () => {
until: "2026-04-01",
expand: true,
});
expect(capturedTimeout).toBe(30_000);
expect(lines).toHaveLength(1);
expect(lines[0]).toContain('"high score row"');
expect(lines[0]).not.toContain('"low score row"');
Expand Down
37 changes: 22 additions & 15 deletions packages/cli/src/commands/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import chalk from "chalk";
import type { Command } from "commander";
import ora from "ora";

const MEMORY_RECALL_TIMEOUT_MS = 30_000;

interface MemoryDeps {
readonly ensureDaemonForSecrets: () => Promise<boolean>;
readonly secretApiCall: (
Expand Down Expand Up @@ -164,21 +166,26 @@ export function registerMemoryCommands(program: Command, deps: MemoryDeps): void
if (!(await deps.ensureDaemonForSecrets())) return;

const spinner = ora("Searching memories...").start();
const { ok, data } = await deps.secretApiCall("POST", "/api/memory/recall", {
query,
keywordQuery: options.keywordQuery,
limit: options.limit,
project: options.project,
type: options.type,
tags: options.tags,
who: options.who,
pinned: options.pinned === true ? true : undefined,
importance_min: options.importanceMin,
since: options.since,
until: options.until,
expand: options.expand === true ? true : undefined,
...(options.agent ? { agentId: options.agent } : {}),
});
const { ok, data } = await deps.secretApiCall(
"POST",
"/api/memory/recall",
{
query,
keywordQuery: options.keywordQuery,
limit: options.limit,
project: options.project,
type: options.type,
tags: options.tags,
who: options.who,
pinned: options.pinned === true ? true : undefined,
importance_min: options.importanceMin,
since: options.since,
until: options.until,
expand: options.expand === true ? true : undefined,
...(options.agent ? { agentId: options.agent } : {}),
},
MEMORY_RECALL_TIMEOUT_MS,
);

const err = typeof data === "object" && data !== null && "error" in data ? data.error : undefined;
if (!ok || typeof err === "string") {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/features/setup-migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export async function runExistingSetupWizard(
if (detection.harnesses.openclaw) detectedHarnesses.push("openclaw");
if (detection.harnesses.opencode) detectedHarnesses.push("opencode");
if (detection.harnesses.codex) detectedHarnesses.push("codex");
if (detection.harnesses.hermesAgent) detectedHarnesses.push("hermes-agent");
const configuredHarnessList = readHarnesses(existingConfig.harnesses);
if (detection.harnesses.ohMyPi || configuredHarnessList.includes("oh-my-pi")) detectedHarnesses.push("oh-my-pi");
if (detection.harnesses.pi || configuredHarnessList.includes("pi")) detectedHarnesses.push("pi");
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/features/setup-shared.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { describe, expect, it } from "bun:test";
import {
DEPLOYMENT_TYPE_CHOICES,
SETUP_HARNESS_CHOICES,
defaultEmbeddingProviderForDeployment,
defaultExtractionProviderForDeployment,
detectExtractionProviderFromAvailable,
getDeploymentExtractionGuidance,
normalizeHarnessList,
resolveSetupExtractionProvider,
} from "./setup-shared.js";

Expand All @@ -13,6 +15,18 @@ describe("setup deployment defaults", () => {
expect(DEPLOYMENT_TYPE_CHOICES).toEqual(["local", "vps", "server"]);
});

it("supports Hermes Agent as a setup harness choice", () => {
expect(SETUP_HARNESS_CHOICES).toContain("hermes-agent");
expect(
normalizeHarnessList(["hermes-agent,claude-code"], {
normalizeChoice: (value, allowed) => {
const s = String(value);
return (allowed as readonly string[]).includes(s) ? (s as (typeof allowed)[number]) : null;
},
}),
).toEqual(["hermes-agent", "claude-code"]);
});

it("defaults embedding provider to native across deployment types", () => {
expect(defaultEmbeddingProviderForDeployment("local")).toBe("native");
expect(defaultEmbeddingProviderForDeployment("vps")).toBe("native");
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/features/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ export async function setupWizard(options: SetupWizardOptions, deps: SetupDeps):
{ value: "openclaw", name: "OpenClaw", checked: existingHarnesses.includes("openclaw") },
{ value: "oh-my-pi", name: "Oh My Pi", checked: existingHarnesses.includes("oh-my-pi") },
{ value: "pi", name: "Pi", checked: existingHarnesses.includes("pi") },
{ value: "hermes-agent", name: "Hermes Agent", checked: existingHarnesses.includes("hermes-agent") },
{
value: "forge",
name: "Forge (native Signet harness)",
Expand Down
24 changes: 19 additions & 5 deletions packages/connector-hermes-agent/hermes-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,29 @@ signet start # ensure daemon is running
Environment variables:
- `SIGNET_DAEMON_URL` — Full daemon URL (default: `http://localhost:3850`)
- `SIGNET_HOST` / `SIGNET_PORT` — Host and port separately
- `SIGNET_AGENT_ID` — Agent scope identifier (default: `default`)
- `SIGNET_AGENT_ID` — Agent scope identifier (default: `hermes-agent`)
- `SIGNET_AGENT_WORKSPACE` — Optional named-agent workspace path (for example `~/.agents/agents/dot`)
- `SIGNET_AGENT_READ_POLICY` — Optional named-agent memory policy for first registration: `shared` (default), `isolated`, or `group`
- `SIGNET_AGENT_POLICY_GROUP` — Required when `SIGNET_AGENT_READ_POLICY=group`

## Tools

| Tool | Description |
|------|-------------|
| `signet_search` | Hybrid memory search (keyword + semantic + knowledge graph) |
| `signet_store` | Store a fact, preference, or decision to memory |
| `signet_profile` | Broad overview of stored memories and context |
| `memory_search` | Hybrid memory search (keyword + semantic + knowledge graph) |
| `memory_store` | Store a fact, preference, or decision to memory |
| `memory_get` | Retrieve a memory by ID |
| `memory_list` | List memories with optional filters |
| `memory_modify` | Edit an existing memory |
| `memory_forget` | Soft-delete a memory |
| `recall` / `remember` | Compatibility aliases for search/store |

`memory_store` exposes the full Signet remember surface, including:

- `content`, `type`, `importance`, `tags`, `pinned`, and `project`
- `hints` for prospective recall hints and alternate phrasings
- `transcript` for lossless source text alongside the saved memory
- `structured.entities`, `structured.aspects`, and `structured.hints` for callers that already extracted graph-ready memory metadata

## How It Works

Expand All @@ -44,4 +58,4 @@ The plugin bridges Hermes Agent's memory lifecycle to the Signet daemon:

3. **Session end** — Sends the conversation transcript to Signet's session-end hook, which queues it for the memory pipeline: extraction, knowledge graph updates, retention decay, and MEMORY.md synthesis.

4. **Explicit tools** — The agent can call `signet_search`, `signet_store`, and `signet_profile` directly during conversation for on-demand memory operations.
4. **Explicit tools** — The agent can call canonical Signet tools such as `memory_search` and `memory_store` directly during conversation for on-demand memory operations. Legacy `signet_*` names are handled for compatibility but are not advertised to the model.
Loading
Loading