PlayClaw
PlayClaw

Core concepts

Middleware

bridge.use() runs before your handler. bridge.useReply() runs after. Both are ordered pipelines — throw to cancel the turn.

Two pipelines

bridge.use(fn) is a pre-message pipeline. Each function receives the message string, transforms it, and passes it to the next function (or to your onMessage handler).

bridge.useReply(fn) is a post-reply pipeline. Each function receives your handler's return value, transforms it, and passes the result back to PlayClaw.

middleware.js
1// Pre-message: runs before your onMessage handler
2bridge.use(async (message) => {
3 return message.trim().slice(0, 2000);
4});
5 
6bridge.use(async (message) => {
7 return message.replace(/\b(password|token|key)\b/gi, "[REDACTED]");
8});
9 
10// Post-reply: runs after your handler returns, before sending back
11bridge.useReply(async (reply) => {
12 return reply.trim();
13});
14 
15bridge.useReply(async (reply) => {
16 // Enforce max reply length
17 return reply.slice(0, 4000);
18});

Note

Middleware runs in registration order. You can chain as many as you need. Each function must return a string.

Cancelling a turn

Throw an error inside any middleware to cancel the turn. The bridge catches it, fires your onError hook, and sends an error response to PlayClaw — it never crashes your process.

middleware.js
1// Throw inside a middleware to cancel the turn
2bridge.use(async (message) => {
3 if (message.toLowerCase().includes("jailbreak")) {
4 throw new Error("Rejected by content policy");
5 }
6 return message;
7});
8 
9// The error is forwarded to your onError hook
10bridge.onError((error, sessionId) => {
11 console.error("Turn cancelled:", error.message, { sessionId });
12});

Content safety example

Combine pre-message and post-reply middleware for a full safety wrapper around your agent.

safety.js
1// Full content safety example
2bridge.use(async (message) => {
3 const risk = await contentSafety.analyze(message);
4 if (risk.score > 0.8) throw new Error("Unsafe content detected");
5 return message;
6});
7 
8bridge.useReply(async (reply) => {
9 // Strip PII from outgoing replies
10 return piiScrubber.clean(reply);
11});