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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,4 @@ docs/internal/

# IDE workspace settings (may contain tokens)
.vscode/
.secret/
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ Product: https://apilium.com/us/products/mayros
Download: https://mayros.apilium.com
Docs: https://apilium.com/us/doc/mayros

## 0.2.1 (2026-03-14)

Memory health tools — conflict detection and digest summaries for proactive memory maintenance.

### MCP Server

- `mayros_memory_conflicts` — scan for duplicate memories and graph-level contradictions (same subject+predicate, different values)
- `mayros_memory_digest` — summarize memory state: total count, category distribution, recent entries, DAG stats
- Parallel fetching in digest tool (content, categories, graph stats, DAG stats via `Promise.all`)
- Both tools degrade gracefully when Cortex is down or DAG is disabled

### CLI

- `mayros memory conflicts` — scan Cortex for contradictions and duplicates (supports `--json`, `--limit`)
- `mayros memory digest` — summarize stored memories with categories and recency (supports `--json`, `--limit`)
- Both commands support `--cortex-host`, `--cortex-port`, `--cortex-token` flags

### Tests

- 13 new tests for memory health tools (duplicates, graph conflicts, empty state, Cortex down, limit capping, sort order, DAG disabled)

---

## 0.2.0 (2026-03-13)

Semantic DAG integration — full audit trail, time-travel, and verifiable history for the knowledge graph.
Expand Down
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,19 +254,44 @@ mayros serve --http

Point any MCP client to `http://127.0.0.1:19100/mcp` (Streamable HTTP) or `http://127.0.0.1:19100/sse` (legacy SSE for older clients).

### Bring persistent memory to any AI coding tool

AI coding CLIs have no memory between sessions. Mayros + [AIngle Cortex](https://github.com/ApiliumCode/aingle) fill that gap: semantic knowledge graph, DAG audit trail, vector search, and ZK proofs — all local-first.

Any MCP-compatible client gets instant access to 21 tools via a single command:

```bash
claude mcp add mayros -- mayros serve --stdio # Claude Code
# Gemini CLI, GitHub Copilot — coming soon
```

Built on the open [Model Context Protocol](https://modelcontextprotocol.io) standard — no vendor lock-in.

### Tools

| Tool | Description |
| --------------------- | ----------------------------------------------------- |
| `mayros_remember` | Store information in persistent semantic memory |
| `mayros_recall` | Search memory by text, tags, or type |
| `mayros_search` | Vector similarity search over memory (HNSW) |
| `mayros_forget` | Delete a memory entry |
| `mayros_budget` | Check token usage and budget status |
| `mayros_policy_check` | Evaluate actions against governance policies |
| `mayros_cortex_query` | Query the knowledge graph by subject/predicate/object |
| `mayros_cortex_store` | Store RDF triples in the knowledge graph |
| `mayros_memory_stats` | STM/LTM/HNSW/graph statistics |
| Tool | Description |
| ------------------------- | ------------------------------------------------------ |
| `mayros_remember` | Store information in persistent semantic memory |
| `mayros_recall` | Search memory by text, tags, or type |
| `mayros_search` | Vector similarity search over memory (HNSW) |
| `mayros_forget` | Delete a memory entry |
| `mayros_memory_stats` | STM/LTM/HNSW/graph statistics |
| `mayros_memory_conflicts` | Scan memory for contradictions and duplicates |
| `mayros_memory_digest` | Summary of stored memories, categories, and DAG status |
| `mayros_cortex_query` | Query the knowledge graph by subject/predicate/object |
| `mayros_cortex_store` | Store RDF triples in the knowledge graph |
| `mayros_budget` | Check token usage and budget status |
| `mayros_policy_check` | Evaluate actions against governance policies |
| `mayros_dag_tips` | Get the current DAG tip hashes (frontier) |
| `mayros_dag_action` | Submit a new action to the DAG |
| `mayros_dag_history` | Query action history for a subject or triple |
| `mayros_dag_chain` | Trace the full chain of ancestors for an action |
| `mayros_dag_stats` | DAG statistics (action count, tip count) |
| `mayros_dag_prune` | Prune old DAG actions by policy |
| `mayros_dag_time_travel` | View graph state at a specific DAG action |
| `mayros_dag_diff` | Compare graph state between two DAG actions |
| `mayros_dag_export` | Export DAG actions as JSON |
| `mayros_dag_verify` | Verify Ed25519 signature of a DAG action |

---

Expand Down Expand Up @@ -346,7 +371,7 @@ Both connect to `ws://127.0.0.1:18789`.
| Transforms | `hayameru` | Deterministic code transforms that bypass LLM (0 tokens, sub-ms) |
| Rate Limit | `tomeru-guard` | Sliding window rate limiter, loop breaker, velocity circuit breaker |
| Hooks | `llm-hooks` | Markdown-defined hook evaluation with safe condition parser |
| MCP Server | `mcp-server` | 9 tools exposed via MCP (memory, budget, governance, graph) |
| MCP Server | `mcp-server` | 21 tools exposed via MCP (memory, graph, DAG, budget, governance) |
| MCP Client | `mcp-client` | Model Context Protocol client (stdio, SSE, WebSocket, HTTP) |
| Economy | `token-economy` | Budget tracking, response cache, prompt cache optimization |
| Bridge | `kakeru-bridge` | Dual-platform coordination (Claude + Codex CLI) |
Expand Down
25 changes: 25 additions & 0 deletions extensions/bash-sandbox/command-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,31 @@ describe("parseCommandChain — subshell detection", () => {
const chain = parseCommandChain("echo '$(not a subshell)'");
expect(chain.commands[0].isSubshell).toBe(false);
});

it("detects <(...) process substitution", () => {
const chain = parseCommandChain("diff <(sort a.txt) <(sort b.txt)");
expect(chain.commands[0].isSubshell).toBe(true);
});

it("detects >(...) process substitution", () => {
const chain = parseCommandChain("tee >(grep error > err.log)");
expect(chain.commands[0].isSubshell).toBe(true);
});

it("does not detect <(...) inside single quotes", () => {
const chain = parseCommandChain("echo '<(not process sub)'");
expect(chain.commands[0].isSubshell).toBe(false);
});

it("does not detect <(...) inside double quotes", () => {
const chain = parseCommandChain('echo "<(not process sub)"');
expect(chain.commands[0].isSubshell).toBe(false);
});

it("does not detect >(...) inside double quotes", () => {
const chain = parseCommandChain('echo ">(not process sub)"');
expect(chain.commands[0].isSubshell).toBe(false);
});
});

// ============================================================================
Expand Down
12 changes: 9 additions & 3 deletions extensions/bash-sandbox/command-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ function tokenize(segment: string): string[] {

/**
* Detect whether a raw command segment contains subshell syntax.
* Checks for `$(...)` and backtick-wrapped `` `...` `` patterns.
* Checks for `$(...)`, backtick-wrapped `` `...` ``, and process
* substitution `<(...)` / `>(...)` patterns.
*/
function detectSubshell(raw: string): boolean {
// Check for $(...) outside quotes
Expand Down Expand Up @@ -213,12 +214,17 @@ function detectSubshell(raw: string): boolean {

if (inSingle) continue;

// $( detected outside single quotes
// $( detected outside single quotes (expands inside double quotes)
if (ch === "$" && i + 1 < raw.length && raw[i + 1] === "(") {
return true;
}

// Backtick detected outside single quotes
// Process substitution: <(...) and >(...) — only outside ALL quotes
if (!inDouble && (ch === "<" || ch === ">") && i + 1 < raw.length && raw[i + 1] === "(") {
return true;
}

// Backtick detected outside single quotes (expands inside double quotes)
if (ch === "`") {
return true;
}
Expand Down
28 changes: 28 additions & 0 deletions extensions/bash-sandbox/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,34 @@ describe("evaluateCommand", () => {
expect(result.action).toBe("warned");
expect(result.allowed).toBe(true);
});

it("warns on subshell / process substitution", async () => {
const { evaluateCommand } = await import("./index.js");
const cfg = bashSandboxConfigSchema.parse({});

const result = evaluateCommand("diff <(sort a.txt) <(sort b.txt)", cfg);
expect(result.action).toBe("warned");
expect(result.allowed).toBe(true);
expect(result.reasons.some((r) => r.includes("Subshell"))).toBe(true);
expect(result.matches.some((m) => m.pattern === "subshell-detected")).toBe(true);
});

it("warns on $(...) subshell", async () => {
const { evaluateCommand } = await import("./index.js");
const cfg = bashSandboxConfigSchema.parse({});

const result = evaluateCommand("echo $(whoami)", cfg);
expect(result.action).toBe("warned");
expect(result.allowed).toBe(true);
});

it("does not warn on quoted process substitution", async () => {
const { evaluateCommand } = await import("./index.js");
const cfg = bashSandboxConfigSchema.parse({});

const result = evaluateCommand("echo '<(not a subshell)'", cfg);
expect(result.action).toBe("allowed");
});
});

// ============================================================================
Expand Down
14 changes: 12 additions & 2 deletions extensions/bash-sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,17 @@ function evaluateCommand(command: string, cfg: BashSandboxConfig): SandboxVerdic
if (match.severity === "warn") warned = true;
}

// 5. Check sudo
// 5. Check subshell / process substitution
for (const cmd of chain.commands) {
if (cmd.isSubshell) {
const msg = `Subshell or process substitution detected (command: ${cmd.executable})`;
reasons.push(msg);
matches.push({ pattern: "subshell-detected", severity: "warn", message: msg });
warned = true;
}
}

// 6. Check sudo
if (!cfg.allowSudo) {
for (const cmd of chain.commands) {
if (cmd.hasSudo) {
Expand All @@ -109,7 +119,7 @@ function evaluateCommand(command: string, cfg: BashSandboxConfig): SandboxVerdic
}
}

// 6. Check domains for network commands (curl, wget, etc.)
// 7. Check domains for network commands (curl, wget, etc.)
if (!cfg.allowCurlToArbitraryDomains) {
const hasNetworkCommand = chain.commands.some((cmd) =>
NETWORK_COMMANDS.has(cmd.executable.toLowerCase()),
Expand Down
9 changes: 8 additions & 1 deletion extensions/mcp-server/dag-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe("DAG MCP Tools", () => {
});

// 8
it("mayros_dag_verify valid signature", async () => {
it("mayros_dag_verify valid signature (POST body)", async () => {
globalThis.fetch = mockFetch({
valid: true,
action_hash: "abc123",
Expand All @@ -129,6 +129,13 @@ describe("DAG MCP Tools", () => {
const result = await tool.execute("id", { hash: "abc123", public_key: "ed25519_key" });
expect(result.content[0]!.text).toContain("VALID");
expect(result.content[0]!.text).toContain("Signature valid");

const callArgs = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0]!;
const url = callArgs[0] as string;
const opts = callArgs[1] as RequestInit;
expect(url).not.toContain("public_key");
expect(opts.method).toBe("POST");
expect(JSON.parse(opts.body as string)).toEqual({ public_key: "ed25519_key" });
});

// 9
Expand Down
Loading
Loading