SLOPSHOPPER

nixie-policy

Spike: a policy mod that guards tool calls

newguard
A shopper browsing a rack in a slop shop
README

<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>

Status

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.

Checks

bun install
bun run check        # format, lint, and the prose check

Layout

  • docs/ holds the overview, the principles, the scope, the decision records, and the designs for what is not built yet. Start with the overview.
  • spikes/ holds throwaway experiments that answer research questions.
Source 1 files
hooks/register.js 41 lines
1// 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