NeoCode x Clawd Integration Example
This is the minimal production-friendly approach:
- keep NeoCode core flow unchanged
- avoid opening dangerous execution hooks
- use
http + observeonly for lifecycle event delivery
Architecture
text
NeoCode runtime hooks
-> http observe (POST JSON)
-> local bridge service
-> Clawd hook/event endpointWhy use a bridge service:
- Clawd protocol changes do not require NeoCode config rewrites
- You can map, throttle, or dedupe events safely
- Bridge failures won't block NeoCode runs
Step 1: Configure NeoCode hooks
Add this to ~/.neocode/config.yaml:
yaml
runtime:
hooks:
enabled: true
user_hooks_enabled: true
default_timeout_sec: 2
default_failure_policy: warn_only
items:
- id: clawd-session-start
enabled: true
point: session_start
scope: user
kind: http
mode: observe
params:
url: "http://127.0.0.1:3101/neocode-hook"
method: POST
include_metadata: false
- id: clawd-before-tool
enabled: true
point: before_tool_call
scope: user
kind: http
mode: observe
params:
url: "http://127.0.0.1:3101/neocode-hook"
method: POST
include_metadata: false
- id: clawd-after-tool
enabled: true
point: after_tool_result
scope: user
kind: http
mode: observe
params:
url: "http://127.0.0.1:3101/neocode-hook"
method: POST
include_metadata: false
- id: clawd-session-end
enabled: true
point: session_end
scope: user
kind: http
mode: observe
params:
url: "http://127.0.0.1:3101/neocode-hook"
method: POST
include_metadata: falseStep 2: Start a local bridge
Example Node.js bridge:
js
// bridge.js
import express from "express";
import fetch from "node-fetch";
const app = express();
app.use(express.json({ limit: "256kb" }));
const CLAWD_ENDPOINT = process.env.CLAWD_ENDPOINT || "http://127.0.0.1:3111/hook";
function mapEvent(payload) {
const point = payload?.point || "unknown";
const toolName = payload?.metadata?.tool_name || "";
if (point === "session_start") return { state: "working", detail: "task started" };
if (point === "before_tool_call") return { state: "tool_running", detail: toolName || "tool call" };
if (point === "after_tool_result") return { state: "tool_done", detail: toolName || "tool result" };
if (point === "session_end") return { state: "idle", detail: "task finished" };
return { state: "working", detail: point };
}
app.post("/neocode-hook", async (req, res) => {
try {
const payload = req.body || {};
const mapped = mapEvent(payload);
await fetch(CLAWD_ENDPOINT, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
source: "neocode",
run_id: payload.run_id || "",
session_id: payload.session_id || "",
point: payload.point || "",
state: mapped.state,
detail: mapped.detail,
ts: payload.triggered_at || new Date().toISOString(),
}),
});
res.status(204).end();
} catch (err) {
console.error("bridge error:", err);
res.status(500).json({ error: "bridge failed" });
}
});
app.listen(3101, "127.0.0.1", () => {
console.log("NeoCode -> Clawd bridge on http://127.0.0.1:3101");
});Step 3: Validate end-to-end
- Start bridge service (
127.0.0.1:3101) - Run a NeoCode task that calls at least one tool
- Verify bridge receives
POST /neocode-hook - Verify Clawd state transitions (
working -> tool_running -> tool_done -> idle)
Troubleshooting
If no callback arrives:
- confirm
runtime.hooks.enabled=true - confirm
kind=httpandmode=observe - confirm
params.urlis absolutehttp/https
Will callback failures stop my run?
- No.
http observeis designed as non-blocking withwarn_only.
Security baseline
- Bind bridge to
127.0.0.1only - Set request body size limits
- Dedupe repeated events (
run_id + point + ts) - Forward only fields needed by Clawd
