Viking Updates

This commit is contained in:
fez5587 committed 2026-03-31 19:02:14 -04:00
1 parent 8c623caa3b
commit 133c0cab47
5 files changed
+234

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# Memory-First Policy (OpenViking)
Use this policy text in any agent `systemPrompt` where you want lower token usage:
1. For non-trivial tasks, call `openviking_search` first with `top_k` 3-5.
2. If relevant matches exist, answer from retrieved evidence instead of broad context expansion.
3. If needed, call `openviking_read` only on the top hits.
4. Keep injected context compact (summary + small excerpts), avoid large raw dumps.
5. After producing reusable output, call `openviking_upsert` with concise notes.
6. If OpenViking is unavailable, continue using normal model fallbacks without blocking.
+13
View File
@@ -6,10 +6,12 @@ This folder is safe to commit to GitHub. It contains config and installers, but
- `templates/opencode.json`
- `templates/oh-my-opencode.json`
- `templates/scripts/openviking-mcp.js`
- `scripts/install-linux.sh`
- `scripts/install-windows.ps1`
- `.env.example`
- `OBSIDIAN_KNOWLEDGE_WORKFLOW.md`
- `MEMORY_FIRST_POLICY.md`
## How to use
@@ -36,6 +38,17 @@ $env:INSTALL_IMPECCABLE="1"
.\scripts\install-windows.ps1
```
## OpenViking MCP bridge
The bundle includes a local OpenViking bridge script (`scripts/openviking-mcp.js`).
- Default OpenViking server URL: `http://127.0.0.1:1933`
- Override with env var: `OPENVIKING_BASE_URL`
- Tools exposed to OpenCode:
- `openviking_search`
- `openviking_read`
- `openviking_upsert`
## Important
- Do not commit `.env`.
+3
View File
@@ -14,6 +14,9 @@ echo "Installing OpenCode config to: $TARGET_DIR"
mkdir -p "$TARGET_DIR"
cp "$TEMPLATES_DIR/opencode.json" "$TARGET_DIR/opencode.json"
cp "$TEMPLATES_DIR/oh-my-opencode.json" "$TARGET_DIR/oh-my-opencode.json"
mkdir -p "$TARGET_DIR/scripts"
cp "$TEMPLATES_DIR/scripts/openviking-mcp.js" "$TARGET_DIR/scripts/openviking-mcp.js"
chmod +x "$TARGET_DIR/scripts/openviking-mcp.js"
echo "Installing optional components (Node/Bun packages)..."
if command -v bun >/dev/null 2>&1; then
+2
View File
@@ -13,6 +13,8 @@ Write-Host "Installing OpenCode config to: $TargetDir"
New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null
Copy-Item (Join-Path $TemplatesDir "opencode.json") (Join-Path $TargetDir "opencode.json") -Force
Copy-Item (Join-Path $TemplatesDir "oh-my-opencode.json") (Join-Path $TargetDir "oh-my-opencode.json") -Force
New-Item -ItemType Directory -Path (Join-Path $TargetDir "scripts") -Force | Out-Null
Copy-Item (Join-Path $TemplatesDir "scripts\openviking-mcp.js") (Join-Path $TargetDir "scripts\openviking-mcp.js") -Force
Write-Host "Installing optional components (Node/Bun packages)..."
if (Get-Command bun -ErrorAction SilentlyContinue) {
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env node
"use strict";
const BASE_URL = (process.env.OPENVIKING_BASE_URL || "http://127.0.0.1:1933").replace(/\/+$/, "");
const SEARCH_PATHS = (process.env.OPENVIKING_SEARCH_PATHS || "/api/v1/search,/find")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
const READ_PATHS = (process.env.OPENVIKING_READ_PATHS || "/api/v1/read,/read")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
const UPSERT_PATHS = (process.env.OPENVIKING_UPSERT_PATHS || "/api/v1/add_resource,/add_resource")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
function writeMessage(msg) {
const body = Buffer.from(JSON.stringify(msg), "utf8");
process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
process.stdout.write(body);
}
async function postWithFallback(paths, payload) {
let lastError = null;
for (const path of paths) {
try {
const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
lastError = new Error(`HTTP ${res.status} on ${path}: ${text.slice(0, 400)}`);
continue;
}
return await res.json();
} catch (err) {
lastError = err;
}
}
throw lastError || new Error("All endpoint attempts failed");
}
function makeTools() {
return [
{
name: "openviking_search",
description: "Search OpenViking memory/context and return top matches.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query." },
top_k: { type: "integer", minimum: 1, maximum: 20, default: 5 },
namespace: { type: "string", description: "Optional namespace/project bucket." },
},
required: ["query"],
},
},
{
name: "openviking_read",
description: "Read a resource from OpenViking by URI or ID.",
inputSchema: {
type: "object",
properties: {
uri: { type: "string" },
id: { type: "string" },
namespace: { type: "string" },
},
},
},
{
name: "openviking_upsert",
description: "Add or update a memory resource in OpenViking.",
inputSchema: {
type: "object",
properties: {
id: { type: "string" },
uri: { type: "string" },
title: { type: "string" },
content: { type: "string" },
namespace: { type: "string" },
metadata: { type: "object" },
},
required: ["content"],
},
},
];
}
async function handleToolCall(name, args) {
if (name === "openviking_search") {
const payload = {
query: args.query,
top_k: args.top_k ?? 5,
namespace: args.namespace,
};
const data = await postWithFallback(SEARCH_PATHS, payload);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
if (name === "openviking_read") {
const payload = {
uri: args.uri,
id: args.id,
namespace: args.namespace,
};
const data = await postWithFallback(READ_PATHS, payload);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
if (name === "openviking_upsert") {
const payload = {
id: args.id,
uri: args.uri,
title: args.title,
content: args.content,
namespace: args.namespace,
metadata: args.metadata,
};
const data = await postWithFallback(UPSERT_PATHS, payload);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
throw new Error(`Unknown tool: ${name}`);
}
async function onRequest(msg) {
const { id, method, params } = msg;
try {
if (method === "initialize") {
writeMessage({
jsonrpc: "2.0",
id,
result: {
protocolVersion: "2024-11-05",
serverInfo: { name: "openviking-mcp-bridge", version: "0.1.0" },
capabilities: { tools: {} },
},
});
return;
}
if (method === "notifications/initialized") {
return;
}
if (method === "tools/list") {
writeMessage({
jsonrpc: "2.0",
id,
result: { tools: makeTools() },
});
return;
}
if (method === "tools/call") {
const toolName = params?.name;
const args = params?.arguments || {};
const result = await handleToolCall(toolName, args);
writeMessage({ jsonrpc: "2.0", id, result });
return;
}
writeMessage({
jsonrpc: "2.0",
id,
error: { code: -32601, message: `Method not found: ${method}` },
});
} catch (err) {
writeMessage({
jsonrpc: "2.0",
id,
error: { code: -32000, message: err && err.message ? err.message : String(err) },
});
}
}
let buffer = Buffer.alloc(0);
process.stdin.on("data", (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
while (true) {
const headerEnd = buffer.indexOf("\r\n\r\n");
if (headerEnd === -1) break;
const headerText = buffer.slice(0, headerEnd).toString("utf8");
const match = headerText.match(/Content-Length:\s*(\d+)/i);
if (!match) {
buffer = buffer.slice(headerEnd + 4);
continue;
}
const length = Number(match[1]);
const total = headerEnd + 4 + length;
if (buffer.length < total) break;
const body = buffer.slice(headerEnd + 4, total).toString("utf8");
buffer = buffer.slice(total);
let msg;
try {
msg = JSON.parse(body);
} catch {
continue;
}
if (msg && msg.jsonrpc === "2.0" && msg.method) {
onRequest(msg);
}
}
});