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
18 changes: 17 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ let package = Package(
),
.init(
name: "MCPServer",
description: "Builds the WaxMCPServer stdio MCP server executable (macOS only)",
description: "Builds the WaxMCPServer executable with stdio and HTTP transports",
enabledTraits: ["MiniLMEmbeddings"]
),
.init(
Expand All @@ -95,6 +95,7 @@ let package = Package(
.package(url: "https://github.com/modelcontextprotocol/swift-sdk.git", from: "0.10.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.7.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.3"),
.package(url: "https://github.com/rensbreur/SwiftTUI.git", branch: "main"),
.package(url: "https://github.com/tuist/Noora.git", from: "0.54.0"),
Expand Down Expand Up @@ -207,6 +208,21 @@ let package = Package(
package: "swift-argument-parser",
condition: .when(traits: ["MCPServer"])
),
.product(
name: "NIOCore",
package: "swift-nio",
condition: .when(traits: ["MCPServer"])
),
.product(
name: "NIOPosix",
package: "swift-nio",
condition: .when(traits: ["MCPServer"])
),
.product(
name: "NIOHTTP1",
package: "swift-nio",
condition: .when(traits: ["MCPServer"])
),
.target(
name: "WaxVectorSearchMiniLM",
condition: .when(traits: ["MiniLMEmbeddings"])
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ npx -y waxmcp@latest mcp install --scope user
The published installer stages the bundled runtime into a stable local directory and
registers `wax-mcp` directly, so steady-state MCP sessions do not launch through raw `npx`.
For the recommended Claude Code prompt and setup flow, see [Resources/docs/wax-mcp-setup.md](Resources/docs/wax-mcp-setup.md).
For the OpenClaw adapter verification pass used in this repo, run [`scripts/verify-openclaw-adapter.sh`](scripts/verify-openclaw-adapter.sh).
For the native-memory operator guide, verifier, and benchmark sweep, see [docs/openclaw-native-memory.md](docs/openclaw-native-memory.md).
The MCP surface now supports managed Markdown round-trips with `markdown_export` / `markdown_sync`,
including `MEMORY.md`, daily notes, and `DREAMS.md` promotion review. `markdown_sync`
also supports `dry_run`, and OpenClaw-oriented promotion thresholds can be overridden on
`session_synthesize` / `memory_promote` or via environment variables.

For remote or team-hosted deployments, `wax-mcp` also supports HTTP transport:

```bash
./.build/debug/wax-mcp --no-embedder --transport http --http-host 127.0.0.1 --http-port 3000
```

### 🔍 WaxRepo
A semantic search TUI for your git history. Index any repository and find code or commits using natural language.
Expand Down
10 changes: 9 additions & 1 deletion Resources/npm/waxmcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ npx -y waxmcp@latest mcp install --scope user
That install flow stages the bundled runtime into a stable local directory and registers the
staged `wax-mcp` binary, so regular MCP sessions do not keep launching through raw `npx`.

> Note: `waxmcp` currently supports Apple Silicon macOS only (`darwin-arm64`).
> Note: the bundled npm runtime currently ships `darwin-arm64` artifacts. The underlying
> `wax-mcp` server itself now supports local Swift builds on macOS and Linux, including
> `--transport http` for remote MCP deployments.

To publish a new version:

Expand Down Expand Up @@ -56,6 +58,12 @@ binary using this search order:
3. `wax-mcp` in PATH
4. `./.build/debug/wax-mcp` (current working directory)

You can also serve Wax over HTTP instead of stdio:

```bash
./.build/debug/wax-mcp --no-embedder --transport http --http-host 127.0.0.1 --http-port 3000
```

### CLI mode

For all other subcommands (remember, recall, search, etc.), the launcher invokes the `wax-cli`
Expand Down
109 changes: 109 additions & 0 deletions Resources/openclaw/wax-memory-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Wax Memory Plugin For OpenClaw

This directory packages the Wax/OpenClaw integration contract that reached `9/10` readiness in the Wax repo:

- native memory-oriented plugin metadata
- a native plugin entry around `registerMemoryCapability`
- a deployment contract that points OpenClaw at the verified `wax-mcp` tool surface
- managed Markdown round-trips via `markdown_export` / `markdown_sync`

The package is structured to be publishable as a native OpenClaw plugin.

## What Is Verified Here

The Wax side is implemented and tested:

- broker-managed OpenClaw memory tools
- `MEMORY.md` / daily note / `DREAMS.md` export
- `markdown_sync` import + reconcile
- `DREAMS.md` approval flow for durable promotion
- HTTP MCP transport for remote deployments

What still needs to happen in a consuming OpenClaw deployment is installing the package, selecting it in `plugins.slots.memory`, and pointing it at a running Wax MCP endpoint.

## Recommended Wax Runtime

Run Wax as a long-lived HTTP MCP service:

```bash
wax-mcp --no-embedder --transport http --http-host 127.0.0.1 --http-port 3000
```

Or use stdio when OpenClaw is colocated with the Wax process:

```bash
wax-mcp --no-embedder
```

## Publish

If you are not publishing under the `@wax` scope, change the package name and `openclaw.install.npmSpec` in `package.json` first.

Validate the archive:

```bash
cd Resources/openclaw/wax-memory-plugin
npm pack --dry-run
```

Publish to npm:

```bash
cd Resources/openclaw/wax-memory-plugin
npm publish --access public
```

## Install In OpenClaw

Install from npm:

```bash
openclaw plugins install @wax/openclaw-wax-memory
```

Or install from a local checkout while iterating:

```bash
openclaw plugins install /absolute/path/to/Resources/openclaw/wax-memory-plugin
```

Select it as the memory plugin in `openclaw.json`:

```json
{
"plugins": {
"entries": {
"wax-memory": {
"enabled": true,
"config": {
"endpoint": "http://127.0.0.1:3000/mcp"
}
}
},
"slots": {
"memory": "wax-memory"
}
}
}
```

Restart the OpenClaw gateway after changing plugin config.

## Files

- `openclaw.plugin.json`
Native plugin metadata and config schema.
- `package.json`
Publishable native OpenClaw package metadata.
- `src/index.ts`
Entry showing the `registerMemoryCapability` hook.

## OpenClaw Notes

The current OpenClaw plugin SDK docs indicate:

- `registerMemoryCapability` is the preferred exclusive memory-plugin API.
- memory plugins may expose `publicArtifacts.listArtifacts(...)` for exported surfaces.
- ACP-backed harness sessions can consume the same Wax MCP endpoint through OpenClaw’s ACP bridge or direct MCP client mode.

This scaffold is intentionally narrow: it avoids inventing OpenClaw host behavior that should live in OpenClaw itself, while still giving the host a concrete integration point.
42 changes: 42 additions & 0 deletions Resources/openclaw/wax-memory-plugin/openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"id": "wax-memory",
"name": "Wax Memory",
"version": "0.1.21",
"kind": "memory",
"description": "Wax-backed memory plugin scaffold for OpenClaw using the verified Wax MCP adapter surface and managed Markdown projections.",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"endpoint": {
"type": "string",
"description": "HTTP MCP endpoint for the Wax broker."
},
"command": {
"type": "string",
"description": "Command used when OpenClaw launches the Wax MCP process directly."
},
"args": {
"type": "array",
"description": "Arguments passed to the Wax MCP command when OpenClaw launches it directly.",
"items": {
"type": "string"
}
}
}
},
"uiHints": {
"endpoint": {
"label": "Wax MCP HTTP Endpoint",
"placeholder": "http://127.0.0.1:3000/mcp"
},
"command": {
"label": "Wax MCP Command",
"placeholder": "wax-mcp"
},
"args": {
"label": "Wax MCP Arguments",
"help": "Used only when OpenClaw launches the Wax MCP process directly."
}
}
}
49 changes: 49 additions & 0 deletions Resources/openclaw/wax-memory-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@wax/openclaw-wax-memory",
"version": "0.1.21",
"type": "module",
"description": "OpenClaw memory plugin for Wax-backed agent memory.",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/christopherkarani/Wax.git",
"directory": "Resources/openclaw/wax-memory-plugin"
},
"homepage": "https://github.com/christopherkarani/Wax/tree/main/Resources/openclaw/wax-memory-plugin",
"bugs": {
"url": "https://github.com/christopherkarani/Wax/issues"
},
"keywords": [
"openclaw",
"plugin",
"memory",
"mcp",
"wax"
],
"publishConfig": {
"access": "public"
},
"openclaw": {
"extensions": [
"./src/index.ts"
],
"compat": {
"pluginApi": ">=2026.3.24-beta.2",
"minGatewayVersion": "2026.3.24-beta.2"
},
"build": {
"target": "node20",
"format": "esm"
},
"install": {
"npmSpec": "@wax/openclaw-wax-memory",
"defaultChoice": "npm",
"minHostVersion": ">=2026.3.24-beta.2"
}
},
"files": [
"src",
"openclaw.plugin.json",
"README.md"
]
}
35 changes: 35 additions & 0 deletions Resources/openclaw/wax-memory-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { registerMemoryCapability } from "openclaw/plugin-sdk/memory-core";

const DEFAULT_HTTP_ENDPOINT = "http://127.0.0.1:3000/mcp";

export default definePluginEntry((api) => {
registerMemoryCapability(api, {
id: "wax-memory",
displayName: "Wax Memory",
description:
"Uses the Wax MCP broker as the canonical memory runtime and exposes managed Markdown artifacts for MEMORY.md, daily notes, and DREAMS.md review.",
publicArtifacts: {
async listArtifacts() {
return [
{
id: "wax-memory-md",
label: "Wax MEMORY.md projection",
kind: "markdown",
},
{
id: "wax-dreams-md",
label: "Wax DREAMS.md review queue",
kind: "markdown",
},
];
},
},
runtime: {
transport: "mcp-http",
endpoint: api.pluginConfig?.endpoint ?? DEFAULT_HTTP_ENDPOINT,
command: api.pluginConfig?.command ?? "wax-mcp",
args: api.pluginConfig?.args ?? ["--no-embedder", "--transport", "http", "--http-port", "3000"],
},
});
});
20 changes: 19 additions & 1 deletion Sources/Wax/Broker/AgentBrokerClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,31 @@ package enum AgentBrokerClient {

let deadline = Date().addingTimeInterval(shutdownTimeoutSeconds)
while Date() < deadline {
if !FileManager.default.fileExists(atPath: configuration.socketPath) {
if try brokerShutdownCompleted(
socketPath: configuration.socketPath,
storePath: configuration.storePath
) {
return
}
Thread.sleep(forTimeInterval: 0.05)
}
}

private static func brokerShutdownCompleted(
socketPath: String,
storePath: String
) throws -> Bool {
guard !FileManager.default.fileExists(atPath: socketPath) else {
return false
}

try StoreLockProbe.preflightExclusiveAccess(
at: URL(fileURLWithPath: storePath),
timeout: .milliseconds(50)
)
Comment on lines +187 to +190
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat lock probe failures as retryable during shutdown wait

brokerShutdownCompleted throws if StoreLockProbe.preflightExclusiveAccess cannot get the lock within 50ms, and shutdownStartedBroker does not catch that exception inside its retry loop. In practice, broker shutdown can leave the lock held briefly after the socket disappears, so this path can fail immediately instead of waiting մինչև the configured deadline, causing spurious shutdown errors even when the broker would have exited cleanly moments later.

Useful? React with 👍 / 👎.

return true
}

private static func sendIfAvailable(
_ request: AgentBrokerRequest,
socketPath: String
Expand Down
20 changes: 18 additions & 2 deletions Sources/Wax/Broker/AgentBrokerProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,19 @@ package enum AgentBrokerPathing {
.appendingPathComponent("broker", isDirectory: true)
}

package static func defaultSessionRoot() -> String {
let env = ProcessInfo.processInfo.environment
if let raw = env["WAX_SESSION_ROOT_DIR"]?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty {
return expandPath(raw)
}
if let raw = env["WAX_SESSION_ROOT"]?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty {
return expandPath(raw)
}
return expandPath(defaultSessionRootPath)
}

package static func resolveBrokerCLIPath(
currentExecutablePath: String
) -> String {
Expand All @@ -249,14 +262,17 @@ package enum AgentBrokerPathing {
brokerExecutablePath: String,
storePath: String,
sessionRootPath: String = defaultSessionRootPath,
socketRootPath: String? = nil,
embedderChoice: String,
noEmbedder: Bool,
requireVector: Bool = false,
embedderTuning: CommandLineEmbedderRuntimeTuning = .fromEnvironment()
) throws -> AgentBrokerConfiguration {
let expandedStore = expandPath(storePath)
let expandedSessionRoot = expandPath(sessionRootPath)
let socketRoot = brokerSocketRoot()
let expandedSessionRoot = sessionRootPath == defaultSessionRootPath
? defaultSessionRoot()
: expandPath(sessionRootPath)
let socketRoot = socketRootPath.map { URL(fileURLWithPath: expandPath($0), isDirectory: true) } ?? brokerSocketRoot()
try FileManager.default.createDirectory(at: socketRoot, withIntermediateDirectories: true)
let binaryIdentity = executableIdentity(path: brokerExecutablePath)
let key = "\(expandedStore)|\(expandedSessionRoot)|\(embedderChoice)|\(noEmbedder)|\(requireVector)|\(embedderTuning.brokerCacheKey)|\(binaryIdentity)"
Expand Down
Loading
Loading