@gcunharodrigues/wrxn 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/wrxn.cjs +59 -1
- package/lib/executor.cjs +1 -1
- package/lib/install.cjs +76 -5
- package/lib/statusline.cjs +97 -0
- package/lib/update.cjs +15 -3
- package/manifest.json +11 -1
- package/migrations/001-recon-to-recon-wrxn.cjs +79 -0
- package/migrations/002-seeded-honesty.cjs +100 -0
- package/package.json +4 -1
- package/payload/.claude/constitution.md +4 -1
- package/payload/.claude/hooks/code-intel-push.cjs +9 -8
- package/payload/.claude/hooks/enforce-push-authority.cjs +6 -5
- package/payload/.claude/hooks/session-end.cjs +86 -46
- package/payload/.claude/hooks/synapse-engine.cjs +41 -14
- package/payload/.claude/skills/synapse/SKILL.md +94 -93
- package/payload/.claude/skills/synapse/assets/README.md +18 -34
- package/payload/.claude/skills/synapse/references/brackets.md +50 -76
- package/payload/.claude/skills/synapse/references/commands.md +43 -100
- package/payload/.claude/skills/synapse/references/domains.md +41 -105
- package/payload/.claude/skills/synapse/references/layers.md +74 -152
- package/payload/.claude/skills/synapse/references/manifest.md +58 -108
- package/payload/.mcp.json +8 -0
- package/payload/.recon-wrxn.json +6 -0
- package/payload/.synapse/global +1 -1
- package/payload/.synapse/routing +1 -1
- package/payload/docs/agents/domain.md +8 -13
- package/payload/docs/statusline-sidecar.sh +30 -0
- package/payload/.recon.json +0 -3
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
// WRXN managed hook — Constitution Article I (Agent Authority).
|
|
5
|
-
// PreToolUse:Bash gate: a remote git op (push / PR / tag push) is
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// PreToolUse:Bash gate: a remote git op (push / PR / tag push) is a deliberate act, held
|
|
6
|
+
// behind a confirmation flag to prevent an accidental push. The op proceeds only once the
|
|
7
|
+
// session confirms intent by setting WRXN_ACTIVE_AGENT=devops in .claude/settings.local.json;
|
|
8
|
+
// an unconfirmed op is denied. Fails OPEN on any internal error (never over-blocks).
|
|
8
9
|
//
|
|
9
10
|
// Contract: reads a PreToolUse hook event as JSON on stdin, writes a decision to stdout.
|
|
10
11
|
// allow → {} (exit 0)
|
|
@@ -33,13 +34,13 @@ function main() {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
if (process.env.WRXN_ACTIVE_AGENT === 'devops') {
|
|
36
|
-
return emit({}); //
|
|
37
|
+
return emit({}); // confirmed
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
return emit({
|
|
40
41
|
decision: 'block',
|
|
41
42
|
reason:
|
|
42
|
-
'Remote git
|
|
43
|
+
'Remote git ops (push / PR / tag) are held behind a deliberate-push confirmation flag, to prevent an accidental push. To confirm intent, set WRXN_ACTIVE_AGENT=devops in .claude/settings.local.json (machine-local), then retry.',
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
// WRXN session-end hook — the episodic writer (wrxn-kernel-10).
|
|
4
|
+
// WRXN session-end hook — the episodic writer + session janitor (wrxn-kernel-10, foundation-honesty-02).
|
|
5
5
|
// SessionEnd. Writes a dated session page into the install's own wiki sessions tier from the
|
|
6
|
-
// captured turn trail, then
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// captured turn trail, then reaps the session's scratch state. Hygiene (foundation-honesty-02):
|
|
7
|
+
// - skip-empty: a session that captured no turns writes NO page;
|
|
8
|
+
// - reap: the consumed trail AND the first-touch marker (.wrxn/history/<sid>.touched, written by
|
|
9
|
+
// code-intel-push) are removed so .wrxn/history/ can't grow without bound;
|
|
10
|
+
// - bound: the sessions tier is capped (WRXN_SESSIONS_MAX, default 50) — oldest pages rotate out.
|
|
11
|
+
// CONTINUITY DOCTRINE: this writer touches ONLY the sessions tier + the session's own history
|
|
12
|
+
// scratch — it NEVER writes OR deletes the continuity baton (.wrxn/continuity/latest.md). That slot
|
|
13
|
+
// has a single writer (the handoff skill); keeping the paths disjoint is the structural fix for the
|
|
14
|
+
// clobber observed live 2026-06-12.
|
|
10
15
|
//
|
|
11
16
|
// Self-contained: ships into installs, MUST NOT import the kernel lib (node stdlib only).
|
|
12
17
|
// Fail-open + side-effect-only: emits nothing useful, never blocks; any fault exits 0 silently.
|
|
13
18
|
//
|
|
14
|
-
// Contract: SessionEnd event JSON on stdin → exit 0. Side effect: a sessions/<date>-<sid>.md page
|
|
19
|
+
// Contract: SessionEnd event JSON on stdin → exit 0. Side effect: a sessions/<date>-<sid>.md page
|
|
20
|
+
// for a non-empty session, plus reaping of that session's trail + touched marker.
|
|
15
21
|
|
|
16
22
|
const fs = require('fs');
|
|
17
23
|
const path = require('path');
|
|
18
24
|
|
|
25
|
+
// Bound the sessions tier to the most-recent N pages (override: WRXN_SESSIONS_MAX).
|
|
26
|
+
const DEFAULT_SESSIONS_MAX = 50;
|
|
27
|
+
|
|
19
28
|
function done() {
|
|
20
29
|
process.exit(0);
|
|
21
30
|
}
|
|
@@ -64,6 +73,32 @@ function readTrail(root, sid) {
|
|
|
64
73
|
return { turns, trail };
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
// Best-effort removal — `force` ignores a missing file; any other fault is swallowed (fail-open).
|
|
77
|
+
function rmQuiet(p) {
|
|
78
|
+
try {
|
|
79
|
+
fs.rmSync(p, { force: true });
|
|
80
|
+
} catch {
|
|
81
|
+
/* best-effort cleanup, never block session close */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Bound the sessions tier: keep at most `max` most-recent dated pages, reaping the oldest. Dated
|
|
86
|
+
// `YYYY-MM-DD-…` slugs sort chronologically, so the oldest are the lexicographically-first ones.
|
|
87
|
+
// Cap = WRXN_SESSIONS_MAX (env) or DEFAULT_SESSIONS_MAX. Self-contained: never throws.
|
|
88
|
+
function capSessions(dir) {
|
|
89
|
+
const max = Number(process.env.WRXN_SESSIONS_MAX) || DEFAULT_SESSIONS_MAX;
|
|
90
|
+
if (!Number.isFinite(max) || max <= 0) return;
|
|
91
|
+
let pages;
|
|
92
|
+
try {
|
|
93
|
+
pages = fs.readdirSync(dir).filter((f) => /^\d{4}-\d{2}-\d{2}-.+\.md$/.test(f)).sort();
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (let i = 0; i < pages.length - max; i++) {
|
|
98
|
+
rmQuiet(path.join(dir, pages[i]));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
67
102
|
function main() {
|
|
68
103
|
let event = {};
|
|
69
104
|
try {
|
|
@@ -76,52 +111,57 @@ function main() {
|
|
|
76
111
|
const root = findInstallRoot();
|
|
77
112
|
if (!root) done();
|
|
78
113
|
|
|
79
|
-
const sid = oneLine(event.session_id || 'session');
|
|
80
|
-
const reason = oneLine(event.reason || 'unknown');
|
|
81
|
-
const date = nowISO().slice(0, 10); // YYYY-MM-DD
|
|
82
|
-
const slug = `${date}-${safeId(event.session_id)}`;
|
|
83
|
-
|
|
84
114
|
const { turns, trail } = readTrail(root, event.session_id);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
|
|
116
|
+
// Skip-empty: a session that captured no turns leaves NO page — the first half of bounding the
|
|
117
|
+
// sessions tier. Only write the page (and only then consume its trail) when there is activity.
|
|
118
|
+
if (turns.length) {
|
|
119
|
+
const sid = oneLine(event.session_id || 'session');
|
|
120
|
+
const reason = oneLine(event.reason || 'unknown');
|
|
121
|
+
const date = nowISO().slice(0, 10); // YYYY-MM-DD
|
|
122
|
+
const slug = `${date}-${safeId(event.session_id)}`;
|
|
123
|
+
|
|
124
|
+
const trailLines = turns.map((t, i) => {
|
|
125
|
+
const tab = t.indexOf('\t');
|
|
126
|
+
const line = tab > -1 ? t.slice(tab + 1) : t;
|
|
127
|
+
return `${i + 1}. ${line}`;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const page = [
|
|
131
|
+
'---',
|
|
132
|
+
`name: ${slug}`,
|
|
133
|
+
`description: Session ${sid} — ${turns.length} turn(s), ended ${reason}`,
|
|
134
|
+
'tier: sessions',
|
|
135
|
+
'source: session-end-hook',
|
|
136
|
+
'---',
|
|
137
|
+
'',
|
|
138
|
+
`# Session ${date} (${sid})`,
|
|
139
|
+
'',
|
|
140
|
+
`- Ended: ${reason}`,
|
|
141
|
+
`- Turns: ${turns.length}`,
|
|
142
|
+
'',
|
|
143
|
+
'## Turn trail',
|
|
144
|
+
...trailLines,
|
|
145
|
+
'',
|
|
146
|
+
].join('\n');
|
|
147
|
+
|
|
148
|
+
const dir = path.join(root, '.wrxn', 'wiki', 'sessions');
|
|
116
149
|
try {
|
|
117
|
-
fs.
|
|
150
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
151
|
+
fs.writeFileSync(path.join(dir, `${slug}.md`), page);
|
|
152
|
+
rmQuiet(trail); // consume the trail only after its page has landed
|
|
153
|
+
capSessions(dir); // rotation: bound the sessions tier (reap the oldest beyond the cap)
|
|
118
154
|
} catch {
|
|
119
|
-
/* trail
|
|
155
|
+
/* page write failed → fail-open; leave the trail intact for no-loss */
|
|
120
156
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
157
|
+
} else {
|
|
158
|
+
rmQuiet(trail); // empty session: nothing to write; drop any stray empty trail file
|
|
123
159
|
}
|
|
124
160
|
|
|
161
|
+
// The first-touch gate marker (written by code-intel-push) is pure per-session scratch — always
|
|
162
|
+
// reap it so .wrxn/history/ can't grow without bound. NEVER touches the continuity baton.
|
|
163
|
+
rmQuiet(path.join(root, '.wrxn', 'history', `${safeId(event.session_id)}.touched`));
|
|
164
|
+
|
|
125
165
|
done();
|
|
126
166
|
}
|
|
127
167
|
|
|
@@ -179,26 +179,49 @@ function readResidentTokens(transcriptPath) {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
// The LIVE window for a session, published by the statusline. UserPromptSubmit hooks receive no
|
|
183
|
+
// model/context-window data, but the statusline payload carries context_window.context_window_size
|
|
184
|
+
// (resolved by Claude Code, refreshed every render — so it tracks a mid-session /model switch). The
|
|
185
|
+
// statusline writes it to a session-scoped /tmp sidecar; we read it back here by session_id.
|
|
186
|
+
// See statusline.sh and memory `handoff-window-defaults-200k`. Returns a positive number or null.
|
|
187
|
+
function readStatuslineWindow(sessionId) {
|
|
188
|
+
if (!sessionId) return null;
|
|
189
|
+
try {
|
|
190
|
+
const p = path.join(os.tmpdir(), `claude-statusline-ctx-${sessionId}.json`);
|
|
191
|
+
const o = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
192
|
+
const w = Number(o && o.context_window_size);
|
|
193
|
+
return Number.isFinite(w) && w > 0 ? w : null;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Model context window, resolved by an explicit precedence (issue 29 + dynamic statusline bridge).
|
|
200
|
+
// On [1m] sessions lastModelUsage is often EMPTY and the transcript model id lacks the [1m] tag, and
|
|
201
|
+
// the hook payload carries no model — so we lean on the statusline (which DOES know the live window):
|
|
202
|
+
// 1. env WRXN_CONTEXT_WINDOW — a positive finite number wins unconditionally (manual force).
|
|
203
|
+
// 2. statusline sidecar — the live per-session window; tracks mid-session model switches.
|
|
204
|
+
// 3. manifest CONTEXT_WINDOW — a positive finite value (when manifestText is supplied).
|
|
205
|
+
// 4. ~/.claude.json lastModelUsage KEYS — a [1m] tag ⇒ 1,000,000 (auto-detect, when present).
|
|
206
|
+
// 5. self-correcting net — resident already past the 200k default ⇒ window is necessarily larger.
|
|
207
|
+
// 6. fallback 200,000.
|
|
208
|
+
// homeDir/manifestText/sessionId/resident overrides keep it testable.
|
|
209
|
+
function modelWindow(cwd, homeDir, manifestText, sessionId, resident) {
|
|
191
210
|
// 1. explicit env override.
|
|
192
211
|
const envWin = Number(process.env.WRXN_CONTEXT_WINDOW);
|
|
193
212
|
if (Number.isFinite(envWin) && envWin > 0) return envWin;
|
|
194
213
|
|
|
195
|
-
// 2.
|
|
214
|
+
// 2. statusline sidecar — the live, authoritative window (dynamic across /model switches).
|
|
215
|
+
const scWin = readStatuslineWindow(sessionId);
|
|
216
|
+
if (scWin) return scWin;
|
|
217
|
+
|
|
218
|
+
// 3. manifest CONTEXT_WINDOW (the engine already reads scalar manifest values).
|
|
196
219
|
if (manifestText != null) {
|
|
197
220
|
const manWin = Number(manifestValue(manifestText, 'CONTEXT_WINDOW'));
|
|
198
221
|
if (Number.isFinite(manWin) && manWin > 0) return manWin;
|
|
199
222
|
}
|
|
200
223
|
|
|
201
|
-
//
|
|
224
|
+
// 4. lastModelUsage [1m] auto-detect.
|
|
202
225
|
try {
|
|
203
226
|
const home = homeDir || process.env.HOME || os.homedir();
|
|
204
227
|
const cfg = JSON.parse(fs.readFileSync(path.join(home, '.claude.json'), 'utf8'));
|
|
@@ -206,10 +229,13 @@ function modelWindow(cwd, homeDir, manifestText) {
|
|
|
206
229
|
const keys = Object.keys(proj.lastModelUsage || {});
|
|
207
230
|
if (keys.some((k) => /\[1m\]/i.test(k))) return 1000000;
|
|
208
231
|
} catch {
|
|
209
|
-
// fall through
|
|
232
|
+
// fall through.
|
|
210
233
|
}
|
|
211
234
|
|
|
212
|
-
//
|
|
235
|
+
// 5. self-correcting net: resident past the 200k default ⇒ a larger (1M) window.
|
|
236
|
+
if (Number.isFinite(resident) && resident > 200000) return 1000000;
|
|
237
|
+
|
|
238
|
+
// 6. fallback.
|
|
213
239
|
return 200000;
|
|
214
240
|
}
|
|
215
241
|
|
|
@@ -295,7 +321,7 @@ function compose(root, event) {
|
|
|
295
321
|
if (ev.transcript_path) {
|
|
296
322
|
const resident = readResidentTokens(ev.transcript_path);
|
|
297
323
|
if (resident != null) {
|
|
298
|
-
const window = modelWindow(ev.cwd || root, process.env.HOME || os.homedir(), manifestText);
|
|
324
|
+
const window = modelWindow(ev.cwd || root, process.env.HOME || os.homedir(), manifestText, ev.session_id, resident);
|
|
299
325
|
const consumed = resident / window;
|
|
300
326
|
const pct = resolveHandoffPct(manifestText);
|
|
301
327
|
if (consumed >= pct) out.push(handoffDirective(consumed, pct));
|
|
@@ -345,6 +371,7 @@ module.exports = {
|
|
|
345
371
|
compose,
|
|
346
372
|
findInstallRoot,
|
|
347
373
|
readResidentTokens,
|
|
374
|
+
readStatuslineWindow,
|
|
348
375
|
modelWindow,
|
|
349
376
|
resolveHandoffPct,
|
|
350
377
|
handoffDirective,
|
|
@@ -1,132 +1,133 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: synapse
|
|
3
|
-
description: "
|
|
3
|
+
description: "Use when someone wants to understand the SYNAPSE context engine, manage its rule domains, tune the token budget or handoff threshold, or troubleshoot why a rule did or didn't inject. Covers the real engine: the three layers (constitution / always-on / keyword-recall), the flat token budget, and the non-blocking handoff directive."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SYNAPSE
|
|
6
|
+
# SYNAPSE context engine
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## What it is
|
|
9
9
|
|
|
10
|
-
SYNAPSE
|
|
10
|
+
SYNAPSE is the per-prompt context-injection engine. On every `UserPromptSubmit` it assembles the
|
|
11
|
+
install's active rule domains into a single `<synapse-rules>` block and returns it as
|
|
12
|
+
`additionalContext`, so each prompt carries the constitution plus the operational rules that apply.
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
- Outputs `<synapse-rules>` XML block appended to each prompt
|
|
14
|
+
It is one self-contained hook — `.claude/hooks/synapse-engine.cjs` — that ships into an install and
|
|
15
|
+
reads only the install's own files (`.claude/constitution.md`, the `.synapse/` domains, and the
|
|
16
|
+
`wrxn.install.json` receipt that marks the install root). It imports nothing from the kernel package.
|
|
17
|
+
It is **fail-open**: any fault (unparseable input, missing file, assembly error) emits an empty
|
|
18
|
+
envelope and injects nothing — the engine never blocks a prompt.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
## The three layers
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
SYNAPSE assembles each prompt from three layers, in this order:
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
| Layer | Source | Fires |
|
|
25
|
+
|-------|--------|-------|
|
|
26
|
+
| **L0 — Constitution** | `.claude/constitution.md` | Always. Never trimmed by the budget. |
|
|
27
|
+
| **L1 — Always-on domains** | `.synapse/<domain>` where `<DOMAIN>_ALWAYS_ON=true` | Every prompt. (Seeded: `global`, `pipeline`.) |
|
|
28
|
+
| **L6 — Keyword-recall domains** | `.synapse/<domain>` with a `<DOMAIN>_RECALL=word,...` list | Only when a trigger word appears in the prompt. (Seeded: `routing`.) |
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
The constitution is rendered from `constitution.md` (article headings + their bullets) and sits
|
|
31
|
+
outside the token budget — it is always kept. Every other active domain contributes a numbered rules
|
|
32
|
+
section. See [the layer model](references/layers.md).
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
## How a domain is defined
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
36
|
+
Domains are registered in `.synapse/manifest` (flat `KEY=VALUE`) and their rules live in a sibling
|
|
37
|
+
file `.synapse/<domain>` (lowercased) as `<DOMAIN>_RULE_<N>=text` lines:
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
```
|
|
40
|
+
# .synapse/manifest
|
|
41
|
+
GLOBAL_STATE=active
|
|
42
|
+
GLOBAL_ALWAYS_ON=true
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
# .synapse/global
|
|
45
|
+
GLOBAL_RULE_0=git push, PR creation, and release tags are deliberate acts held behind a confirmation flag (anti-accidental-push) — `devops` is a dispatch-phase label, not an authority.
|
|
46
|
+
GLOBAL_RULE_1=The unit of work is an issue with explicit acceptance criteria.
|
|
47
|
+
```
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
| `*synapse domains` | List all registered domains |
|
|
41
|
-
| `*synapse debug` | Show detailed debug info (manifest parse, load times, rule counts) |
|
|
42
|
-
| `*synapse help` | Show all available synapse commands |
|
|
43
|
-
| `*brief` | Switch to brief response mode |
|
|
44
|
-
| `*dev` | Switch to developer mode (code-focused) |
|
|
45
|
-
| `*review` | Switch to code review mode |
|
|
49
|
+
A domain loads only when `<DOMAIN>_STATE=active`. An always-on domain sets `_ALWAYS_ON=true`; a
|
|
50
|
+
keyword domain sets `_RECALL=word1,word2`. See [the manifest format](references/manifest.md) and
|
|
51
|
+
[domains & rule files](references/domains.md).
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
## The token budget
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
Everything except the constitution is trimmable. A single flat budget (`RULES_BUDGET_TOKENS`,
|
|
56
|
+
default 600; override `WRXN_RULES_BUDGET`) caps the trimmable sections; when the assembled rules
|
|
57
|
+
exceed it, whole sections are dropped lowest-priority-first and a visible `[SYNAPSE-RULES-TRIM]`
|
|
58
|
+
marker records what was dropped. One budget, applied flat. See
|
|
59
|
+
[token budget & handoff](references/brackets.md).
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
## The handoff directive
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
When real consumed context reaches the handoff threshold (`HANDOFF_PCT`, default 0.40; override
|
|
64
|
+
`WRXN_HANDOFF_PCT`) of the model window, SYNAPSE appends a **non-blocking** `[HANDOFF REQUIRED]`
|
|
65
|
+
directive: finish the current request, run the handoff skill to write the baton, then `/clear` and
|
|
66
|
+
resume in a fresh session. It never refuses work. The math runs on real token usage (resident tokens
|
|
67
|
+
from the transcript ÷ the resolved model window), not an assumed window. See
|
|
68
|
+
[token budget & handoff](references/brackets.md).
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
## Output shape
|
|
58
71
|
|
|
59
72
|
```
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
v imports
|
|
63
|
-
.aiox-core/core/synapse/ # Layer 2: Engine Modules
|
|
64
|
-
|-- engine.js # SynapseEngine class
|
|
65
|
-
|-- layers/ # 8 layer processors (L0-L7)
|
|
66
|
-
|-- session/session-manager.js # Session state (JSON v2.0)
|
|
67
|
-
|-- domain/domain-loader.js # Manifest + domain parser
|
|
68
|
-
|-- context/context-tracker.js # Bracket calculation
|
|
69
|
-
|-- memory/memory-bridge.js # Pro-gated MIS consumer
|
|
70
|
-
|-- output/formatter.js # <synapse-rules> XML
|
|
71
|
-
|
|
|
72
|
-
v reads/writes
|
|
73
|
-
.synapse/ # Layer 3: Runtime Data
|
|
74
|
-
|-- manifest # Central domain registry (KEY=VALUE)
|
|
75
|
-
|-- constitution, global, context # Core domains (L0, L1)
|
|
76
|
-
|-- agent-*, workflow-* # Scoped domains (L2, L3)
|
|
77
|
-
|-- commands # Star-command definitions (L7)
|
|
78
|
-
|-- sessions/, cache/ # Session state (gitignored)
|
|
79
|
-
|
|
|
80
|
-
v user-invoked
|
|
81
|
-
.claude/commands/synapse/ # Layer 4: CRUD Commands + Skill Docs
|
|
82
|
-
|-- manager.md # Router/dispatcher
|
|
83
|
-
|-- tasks/ (6 tasks) # create, add, edit, toggle, command, suggest
|
|
84
|
-
```
|
|
73
|
+
<synapse-rules>
|
|
85
74
|
|
|
86
|
-
|
|
75
|
+
[CONSTITUTION] (NON-NEGOTIABLE)
|
|
76
|
+
Article I — Agent Authority (NON-NEGOTIABLE)
|
|
77
|
+
git push, PR creation, and release tags are deliberate acts held behind a confirmation flag (anti-accidental-push) — `devops` is a dispatch-phase label, not an authority.
|
|
78
|
+
...
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
[GLOBAL]
|
|
81
|
+
1. git push, PR creation, and release tags are deliberate acts held behind a confirmation flag ...
|
|
82
|
+
2. The unit of work is an issue with explicit acceptance criteria ...
|
|
89
83
|
|
|
90
|
-
|
|
84
|
+
[RECALL: routing]
|
|
85
|
+
1. git push, PR creation, and release tags are deliberate acts held behind a confirmation flag ...
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
|-------|-------------|
|
|
94
|
-
| [domains.md](references/domains.md) | Domain types (L0-L7), KEY=VALUE format, creation guide |
|
|
95
|
-
| [commands.md](references/commands.md) | Star-commands, *synapse sub-commands, CRUD operations |
|
|
96
|
-
| [manifest.md](references/manifest.md) | Manifest format specification, all valid keys |
|
|
97
|
-
| [brackets.md](references/brackets.md) | Context bracket system, token budgets, layer activation |
|
|
98
|
-
| [layers.md](references/layers.md) | 8-layer processor architecture, priority, conflict resolution |
|
|
87
|
+
[SYNAPSE-RULES-TRIM] ROUTING dropped over the 600-token rules budget
|
|
99
88
|
|
|
100
|
-
|
|
89
|
+
[HANDOFF REQUIRED]
|
|
90
|
+
Context is at ~42% of the model window (>= the 40% handoff threshold). NON-BLOCKING — do NOT stop work:
|
|
91
|
+
1. Finish the current request.
|
|
92
|
+
2. Run the handoff skill to write the baton (a compact handoff document).
|
|
93
|
+
3. Tell the operator to /clear and open a fresh session, where the baton injects on resume.
|
|
101
94
|
|
|
102
|
-
|
|
95
|
+
</synapse-rules>
|
|
96
|
+
```
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
The trim marker appears only when a section was dropped; the handoff directive only at/above the
|
|
99
|
+
threshold. When no domains are active the engine injects nothing.
|
|
106
100
|
|
|
107
|
-
|
|
101
|
+
## Configuration
|
|
108
102
|
|
|
109
|
-
|
|
103
|
+
| Knob | Where | Effect |
|
|
104
|
+
|------|-------|--------|
|
|
105
|
+
| `RULES_BUDGET_TOKENS` | `.synapse/manifest` | Trimmable-rules token ceiling (default 600). |
|
|
106
|
+
| `HANDOFF_PCT` | `.synapse/manifest` | Handoff threshold as a window fraction (default 0.40). |
|
|
107
|
+
| `CONTEXT_WINDOW` | `.synapse/manifest` | Pin the model window (tokens) for the handoff math. |
|
|
108
|
+
| `WRXN_RULES_BUDGET` | env | Overrides `RULES_BUDGET_TOKENS`. |
|
|
109
|
+
| `WRXN_HANDOFF_PCT` | env | Overrides `HANDOFF_PCT`. |
|
|
110
|
+
| `WRXN_CONTEXT_WINDOW` | env | Forces the model window unconditionally. |
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
See [invocation & configuration](references/commands.md).
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|---------|---------|
|
|
115
|
-
| `*synapse create` | Create new domain + manifest entry |
|
|
116
|
-
| `*synapse add` | Add rule to existing domain |
|
|
117
|
-
| `*synapse edit` | Edit or remove rule by index |
|
|
118
|
-
| `*synapse toggle` | Toggle domain active/inactive |
|
|
119
|
-
| `*synapse command` | Create new star-command |
|
|
120
|
-
| `*synapse suggest` | Suggest best domain for a rule |
|
|
114
|
+
## References
|
|
121
115
|
|
|
122
|
-
|
|
116
|
+
| Guide | Covers |
|
|
117
|
+
|-------|--------|
|
|
118
|
+
| [The layer model](references/layers.md) | the L0/L1/L6 assembly, ordering, constitution rendering, output format |
|
|
119
|
+
| [The manifest format](references/manifest.md) | the `.synapse/manifest` keys and scalars |
|
|
120
|
+
| [Domains & rule files](references/domains.md) | the seeded domains, the `RULE_N` format, adding a domain |
|
|
121
|
+
| [Token budget & handoff](references/brackets.md) | the flat budget governor + the non-blocking handoff |
|
|
122
|
+
| [Invocation & configuration](references/commands.md) | how the hook is wired, the env/manifest knobs, troubleshooting |
|
|
123
|
+
| [Templates](assets/README.md) | domain-file and manifest-entry templates |
|
|
123
124
|
|
|
124
|
-
## Key
|
|
125
|
+
## Key files
|
|
125
126
|
|
|
126
127
|
| File | Purpose |
|
|
127
128
|
|------|---------|
|
|
128
|
-
| `.claude/hooks/synapse-engine.
|
|
129
|
-
| `.
|
|
130
|
-
| `.synapse/manifest` | Domain registry
|
|
131
|
-
| `.synapse/
|
|
132
|
-
| `.
|
|
129
|
+
| `.claude/hooks/synapse-engine.cjs` | The engine (UserPromptSubmit hook). |
|
|
130
|
+
| `.claude/constitution.md` | L0 source — the non-negotiable articles. |
|
|
131
|
+
| `.synapse/manifest` | Domain registry + budget/handoff scalars. |
|
|
132
|
+
| `.synapse/global`, `.synapse/pipeline` | Seeded always-on (L1) domains. |
|
|
133
|
+
| `.synapse/routing` | Seeded keyword-recall (L6) domain. |
|
|
@@ -1,50 +1,34 @@
|
|
|
1
|
-
# SYNAPSE
|
|
1
|
+
# SYNAPSE templates
|
|
2
2
|
|
|
3
|
-
Templates for
|
|
3
|
+
Templates for adding a SYNAPSE domain by hand. There is no interactive creator — a domain is two
|
|
4
|
+
edits: a rule file in `.synapse/` and a registry entry in `.synapse/manifest`. See
|
|
5
|
+
[domains & rule files](../references/domains.md) and [the manifest format](../references/manifest.md).
|
|
4
6
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
Templates are maintained as the single source of truth in the CRUD commands directory:
|
|
8
|
-
|
|
9
|
-
| Template | Location |
|
|
10
|
-
|----------|----------|
|
|
11
|
-
| **Domain template** | `.claude/commands/synapse/templates/domain-template` |
|
|
12
|
-
| **Manifest entry template** | `.claude/commands/synapse/templates/manifest-entry-template` |
|
|
13
|
-
|
|
14
|
-
These templates are used by the `*synapse create` command to scaffold new domains.
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
To create a new domain using these templates, run:
|
|
7
|
+
## Domain rule file — `.synapse/<name>`
|
|
19
8
|
|
|
20
9
|
```
|
|
21
|
-
|
|
10
|
+
# Domain: <name> (<always-on L1 | keyword-recall L6>) — <one-line description>
|
|
11
|
+
<NAME>_RULE_0=<first rule>
|
|
12
|
+
<NAME>_RULE_1=<second rule>
|
|
22
13
|
```
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
`<NAME>` is the uppercase prefix; the file is named for its lowercase form. Rules are numbered
|
|
16
|
+
ascending from 0.
|
|
25
17
|
|
|
26
|
-
##
|
|
18
|
+
## Manifest entry — `.synapse/manifest`
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
Always-on (loads on every prompt):
|
|
29
21
|
|
|
30
22
|
```
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# Created: {CURRENT_DATE}
|
|
34
|
-
# Description: {DESCRIPTION}
|
|
35
|
-
# ==========================================
|
|
36
|
-
|
|
37
|
-
# Rules
|
|
38
|
-
{DOMAIN_KEY}_RULE_0={FIRST_RULE}
|
|
23
|
+
<NAME>_STATE=active
|
|
24
|
+
<NAME>_ALWAYS_ON=true
|
|
39
25
|
```
|
|
40
26
|
|
|
41
|
-
|
|
27
|
+
Keyword-recall (loads only when a trigger word appears in the prompt):
|
|
42
28
|
|
|
43
29
|
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{DOMAIN_KEY}_RECALL={KEYWORDS}
|
|
47
|
-
{DOMAIN_KEY}_EXCLUDE=
|
|
30
|
+
<NAME>_STATE=active
|
|
31
|
+
<NAME>_RECALL=word1,word2
|
|
48
32
|
```
|
|
49
33
|
|
|
50
|
-
|
|
34
|
+
Set `<NAME>_STATE=inactive` (or remove the entry) to stop loading the domain.
|