Skip to content

Commit 3efcd73

Browse files
author
Claudia
committed
fix: address CodeRabbit and Copilot review findings
- Restore .coderabbit.yaml (accidentally deleted) - Restore --version flag in cmd/membraned/main.go (accidentally reverted) - Fix openclaw.plugin.json to use proper JSON Schema with required - Harden validateConfig against NaN, negative, OOB values - Make event ref unique (timestamp-based) to avoid collisions - Show payload summary in context injection instead of just record ID - Fix getStatus() false disconnect on metrics failure - Add language specifiers to README code blocks - Add validation edge case tests (26/26 pass)
1 parent 4eba17d commit 3efcd73

6 files changed

Lines changed: 107 additions & 17 deletions

File tree

.coderabbit.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
2+
language: "en-US"
3+
4+
reviews:
5+
profile: "assertive"
6+
high_level_summary: true
7+
auto_review:
8+
enabled: true
9+
drafts: false
10+
ignore_title_keywords:
11+
- "wip"
12+
- "draft"
13+
auto_pause_after_reviewed_commits: 0
14+
path_filters:
15+
- "!go.sum"
16+
- "!**/package-lock.json"
17+
- "!**/pnpm-lock.yaml"
18+
- "!**/yarn.lock"
19+
- "!**/*.snap"
20+
- "!**/dist/**"
21+
- "!**/build/**"
22+
- "!**/.vitepress/dist/**"
23+
- "!**/coverage/**"
24+
- "!**/.next/**"
25+
- "!**/site/**"
26+
- "!**/docs/.vitepress/cache/**"
27+
- "!**/*.min.js"
28+
- "!**/*.generated.*"
29+
- "!**/*pb.go"
30+
31+
chat:
32+
auto_reply: true
33+
34+
knowledge_base:
35+
code_guidelines:
36+
enabled: true
37+
filePatterns:
38+
- "CONTRIBUTING.md"
39+
- "README.md"
40+
- "docs/**/*.md"

clients/openclaw/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ npx brainplex init # Auto-detects and configures all plugins
5858

5959
Your agent can search episodic memory:
6060

61-
```
61+
```javascript
6262
membrane_search("what happened in yesterday's meeting", { limit: 10 })
6363
```
6464

@@ -70,14 +70,14 @@ When `auto_context: true`, the plugin injects relevant memories into the agent's
7070

7171
Check connection status:
7272

73-
```
73+
```text
7474
/membrane
7575
→ Membrane: connected (localhost:4222) | 1,247 records | 3 memory types
7676
```
7777

7878
## Architecture
7979

80-
```
80+
```text
8181
OpenClaw Agent
8282
8383
├── after_agent_reply ──→ ingestEvent()

clients/openclaw/openclaw.plugin.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
"name": "membrane_search",
1313
"description": "Search episodic memory in Membrane for relevant context",
1414
"parameters": {
15-
"query": { "type": "string", "description": "Natural language query to search memories" },
16-
"limit": { "type": "number", "description": "Maximum results to return (default: 5)" },
17-
"memory_types": { "type": "array", "description": "Filter by memory type: event, tool_output, observation, working_state" },
18-
"min_salience": { "type": "number", "description": "Minimum salience score (0-1, default: 0.3)" }
15+
"type": "object",
16+
"properties": {
17+
"query": { "type": "string", "description": "Natural language query to search memories" },
18+
"limit": { "type": "number", "description": "Maximum results to return (default: 5)" },
19+
"memory_types": { "type": "array", "description": "Filter by memory type: event, tool_output, observation, working_state" },
20+
"min_salience": { "type": "number", "description": "Minimum salience score (0-1, default: 0.3)" }
21+
},
22+
"required": ["query"]
1923
}
2024
}
2125
],

clients/openclaw/src/index.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@ export function validateConfig(raw: Record<string, unknown> | undefined): Partia
2525
if (typeof raw.grpc_endpoint === "string") result.grpc_endpoint = raw.grpc_endpoint;
2626
if (typeof raw.default_sensitivity === "string") result.default_sensitivity = raw.default_sensitivity;
2727
if (typeof raw.auto_context === "boolean") result.auto_context = raw.auto_context;
28-
if (typeof raw.context_limit === "number") result.context_limit = raw.context_limit;
29-
if (typeof raw.min_salience === "number") result.min_salience = raw.min_salience;
28+
if (typeof raw.context_limit === "number" && Number.isInteger(raw.context_limit) && raw.context_limit > 0) {
29+
result.context_limit = raw.context_limit;
30+
}
31+
if (typeof raw.min_salience === "number" && Number.isFinite(raw.min_salience) && raw.min_salience >= 0 && raw.min_salience <= 1) {
32+
result.min_salience = raw.min_salience;
33+
}
3034
if (Array.isArray(raw.context_types)) {
31-
result.context_types = raw.context_types.filter((t): t is string => typeof t === "string");
35+
const filtered = raw.context_types.filter((t): t is string => typeof t === "string");
36+
if (filtered.length > 0) result.context_types = filtered;
3237
}
3338
return result;
3439
}
@@ -94,8 +99,12 @@ export class OpenClawMembranePlugin {
9499
{ sensitivity, source, tags },
95100
);
96101
} else {
97-
// Event: ref = sessionKey or hook (unique reference for the event)
98-
const ref = event.sessionKey ?? event.hook;
102+
// Event: ref must be unique per ingestion to avoid collisions
103+
const ref = [
104+
event.sessionKey ?? source,
105+
event.hook,
106+
event.timestamp ?? String(Date.now()),
107+
].join(":");
99108
await this.client.ingestEvent(event.hook, ref, {
100109
summary: summarize(event),
101110
sensitivity,
@@ -153,9 +162,12 @@ export class OpenClawMembranePlugin {
153162

154163
if (records.length === 0) return null;
155164

156-
const lines = records.map((r: MemoryRecord, i: number) =>
157-
`${i + 1}. [${r.type}] ${r.id}`
158-
);
165+
const lines = records.map((r: MemoryRecord, i: number) => {
166+
// Extract human-readable summary from payload when available
167+
const payload = r.payload as Record<string, unknown> | undefined;
168+
const summary = payload?.summary ?? payload?.content ?? r.id;
169+
return `${i + 1}. [${r.type}] ${String(summary)}`;
170+
});
159171
return `Episodic memory from Membrane:\n${lines.join("\n")}`;
160172
} catch (err) {
161173
this.log.debug(`[membrane] Context injection skipped: ${err instanceof Error ? err.message : String(err)}`);
@@ -172,8 +184,9 @@ export class OpenClawMembranePlugin {
172184
try {
173185
const metrics = await this.client.getMetrics();
174186
return { connected: true, endpoint: this.config.grpc_endpoint, metrics };
175-
} catch {
176-
return { connected: false, endpoint: this.config.grpc_endpoint };
187+
} catch (err) {
188+
this.log.debug(`[membrane] Metrics unavailable: ${err instanceof Error ? err.message : String(err)}`);
189+
return { connected: true, endpoint: this.config.grpc_endpoint };
177190
}
178191
}
179192
}

clients/openclaw/test/plugin.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ describe("validateConfig", () => {
4444
const result = validateConfig({ context_types: ["event", 42, "observation"] });
4545
expect(result.context_types).toEqual(["event", "observation"]);
4646
});
47+
48+
it("rejects NaN and negative context_limit", () => {
49+
expect(validateConfig({ context_limit: NaN })).toEqual({});
50+
expect(validateConfig({ context_limit: -1 })).toEqual({});
51+
expect(validateConfig({ context_limit: 0 })).toEqual({});
52+
expect(validateConfig({ context_limit: 3.5 })).toEqual({});
53+
});
54+
55+
it("rejects out-of-range min_salience", () => {
56+
expect(validateConfig({ min_salience: -0.1 })).toEqual({});
57+
expect(validateConfig({ min_salience: 1.5 })).toEqual({});
58+
expect(validateConfig({ min_salience: NaN })).toEqual({});
59+
});
60+
61+
it("accepts valid min_salience", () => {
62+
expect(validateConfig({ min_salience: 0 })).toEqual({ min_salience: 0 });
63+
expect(validateConfig({ min_salience: 0.5 })).toEqual({ min_salience: 0.5 });
64+
expect(validateConfig({ min_salience: 1 })).toEqual({ min_salience: 1 });
65+
});
66+
67+
it("drops empty context_types array to preserve defaults", () => {
68+
const result = validateConfig({ context_types: [42, true] });
69+
expect(result.context_types).toBeUndefined();
70+
});
4771
});
4872

4973
describe("OpenClawMembranePlugin", () => {

cmd/membraned/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"flag"
6+
"fmt"
67
"log"
78
"os"
89
"os/signal"
@@ -12,12 +13,20 @@ import (
1213
"github.com/GustyCube/membrane/pkg/membrane"
1314
)
1415

16+
var version = "dev"
17+
1518
func main() {
1619
configPath := flag.String("config", "", "path to YAML config file")
1720
dbPath := flag.String("db", "", "SQLite database path (overrides config)")
1821
addr := flag.String("addr", "", "gRPC listen address (overrides config)")
22+
showVersion := flag.Bool("version", false, "print version and exit")
1923
flag.Parse()
2024

25+
if *showVersion {
26+
fmt.Printf("membraned %s\n", version)
27+
return
28+
}
29+
2130
// Load configuration.
2231
var cfg *membrane.Config
2332
if *configPath != "" {

0 commit comments

Comments
 (0)