SLOPSHOPPER

nixie-hold-poll

Spike: a tool.call hook that holds a call across repeated long-polls

newguardnetwork
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 51 lines
1// Holds MARK_HOLD calls until the owner's decision service answers. Each long-poll stays under the
2// 30 s cap on $.http.fetch, and the wait inside it does not count against the hook's 10 s limit.
3async function readDecision($, url) {
4  const response = await $.http.fetch(url);
5  return JSON.parse(response.text).decision;
6}
7
8async function waitForDecision($, request, poll = 1) {
9  const decision = await readDecision($, request.url);
10  await $.ui.log(
11    `nixie poll ${poll} decision=${decision} budget=${JSON.stringify(request.budget)}`,
12    {
13      to: 'debug',
14    },
15  );
16  if (decision === 'pending') {
17    return waitForDecision($, request, poll + 1);
18  }
19  return { decision, polls: poll };
20}
21
22async function handleToolCall($, e, next) {
23  if (!e.command.includes('MARK_HOLD')) {
24    return next(e);
25  }
26  const base = await $.env.get('NIXIE_DECIDER_URL'),
27    started = Date.now(),
28    verdict = await waitForDecision($, {
29      budget: next.budget,
30      url: `${base}/decide?tool_use_id=${e.tool_use_id}`,
31    });
32  if (verdict.decision === 'allow') {
33    return next(e);
34  }
35  return {
36    deny: `nixie-hold-poll: the owner refused this command after ${Date.now() - started} ms and ${verdict.polls} polls.`,
37  };
38}
39
40function handleFailure($, e, next) {
41  return {
42    deny: `nixie policy failed (${next.error.kind}: ${next.error.message}), so the command did not run.`,
43  };
44}
45
46export function register(on) {
47  // `.catch` here is the mods API's error handler on a registration, not a promise method.
48  // oxlint-disable-next-line promise/prefer-await-to-then
49  on('tool.call', { tool: 'Bash' }, handleToolCall).catch(handleFailure);
50}
51