Spike: a policy mod that guards tool calls

<h1>nixie</h1>
A personal assistant platform that its owner runs and controls: every rule readable and editable, every decision recorded, and personal data kept on the owner's own hosts.
<a href="./docs/README.md">Documentation</a> • <a href="./docs/README.md#decisions">Decisions</a> • <a href="./AGENTS.md">Agent Guidelines</a>
nixie is in Phase 2, Research, and has no product code. The work runs in 4 phases: brainstorm, research, design, and decide. Decisions are recorded as research settles each question.
bun install
bun run check # format, lint, and the prose check
hooks/register.js 41 lines1// Each marker in a Bash command picks one policy outcome, so one prompt exercises all of them.
2function checkBash($, e, next) {
3 if (e.command.includes('MARK_DENY')) {
4 return { deny: 'nixie-policy denied this command.' };
5 }
6 if (e.command.includes('MARK_REWRITE')) {
7 return next({ ...e, command: 'echo rewritten-by-nixie' });
8 }
9 if (e.command.includes('MARK_ANSWER_TEXT')) {
10 return { result: 'answered-by-nixie as text' };
11 }
12 if (e.command.includes('MARK_ANSWER')) {
13 // The result takes the tool's own output shape, here Bash's.
14 return { result: { interrupted: false, stderr: '', stdout: 'answered-by-nixie' } };
15 }
16 return next(e);
17}
18
19async function handleToolCall($, e, next) {
20 const target = e.command ?? e.file_path ?? '';
21 await $.ui.log(`nixie tool.call ${e.tool} ${JSON.stringify(target)}`, { to: 'debug' });
22 if (e.tool !== 'Bash') {
23 return next(e);
24 }
25 return checkBash($, e, next);
26}
27
28async function handleToolCheck($, e, next) {
29 const decided = await next(e);
30 await $.ui.log(`nixie tool.check ${e.tool} upstream=${JSON.stringify(decided)}`, { to: 'debug' });
31 if (e.tool === 'Bash' && e.input.command.includes('MARK_CHECK')) {
32 return { decision: 'deny', reason: 'nixie-policy tool.check denied this command.' };
33 }
34 return decided;
35}
36
37export function register(on) {
38 on('tool.call', handleToolCall);
39 on('tool.check', handleToolCheck);
40}
41