-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathwrit-architecture-flowchart.html
More file actions
866 lines (823 loc) · 33 KB
/
Copy pathwrit-architecture-flowchart.html
File metadata and controls
866 lines (823 loc) · 33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Writ Architecture</title>
<style>
:root {
--bg: #f7f5f0;
--panel: #ffffff;
--panel-2: #fafaf7;
--ink: #1a1a1a;
--ink-soft: #4a4a4a;
--line: #d8d4cc;
--line-soft: #ebe7dd;
--blue: #0a4a7f;
--blue-soft: #e7f0f7;
--amber: #b45309;
--amber-soft: #fdf3e2;
--green: #047857;
--green-soft: #e6f4ee;
--purple: #6d28d9;
--purple-soft: #f0e9fc;
--shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.05);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.wrap {
max-width: 1180px;
margin: 0 auto;
padding: 48px 28px 96px;
}
header.hero {
margin-bottom: 48px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 12px;
color: var(--blue);
font-weight: 600;
}
h1 {
font-size: 42px;
line-height: 1.1;
margin: 8px 0 16px;
font-weight: 700;
color: var(--ink);
}
.pitch {
font-size: 19px;
color: var(--ink-soft);
max-width: 780px;
}
.pitch strong { color: var(--ink); }
/* Pipeline diagram */
.pipeline {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
padding: 24px;
margin: 32px 0 48px;
box-shadow: var(--shadow);
}
.pipeline-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 18px;
flex-wrap: wrap;
gap: 12px;
}
.pipeline-title {
font-size: 18px;
font-weight: 600;
}
.pipeline-sub {
font-size: 13px;
color: var(--ink-soft);
}
.stages {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
align-items: stretch;
}
.stage {
background: var(--panel-2);
border: 1px solid var(--line);
border-radius: 10px;
padding: 14px 12px;
position: relative;
cursor: default;
transition: transform 120ms ease, border-color 120ms ease;
}
.stage:hover {
border-color: var(--blue);
transform: translateY(-1px);
}
.stage:not(:last-child)::after {
content: "";
position: absolute;
right: -10px;
top: 50%;
width: 14px;
height: 2px;
background: var(--line);
transform: translateY(-50%);
z-index: 1;
}
.stage-n {
font-size: 11px;
font-weight: 700;
color: var(--blue);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.stage-name {
font-size: 14px;
font-weight: 600;
margin: 4px 0 6px;
}
.stage-impl {
font-size: 12px;
color: var(--ink-soft);
}
/* Tab shell (left rail + active content) */
.tab-shell {
display: grid;
grid-template-columns: 260px 1fr;
gap: 28px;
align-items: start;
}
.tab-list {
position: sticky;
top: 72px;
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 12px;
box-shadow: var(--shadow);
}
.tab-btn {
text-align: left;
background: transparent;
border: 1px solid transparent;
border-radius: 8px;
padding: 9px 12px;
font: inherit;
font-size: 13.5px;
color: var(--ink-soft);
cursor: pointer;
transition: background 120ms, color 120ms, border-color 120ms;
}
.tab-btn:hover { background: var(--panel-2); color: var(--ink); }
.tab-btn.active {
background: var(--blue);
color: white;
border-color: var(--blue);
font-weight: 600;
}
@media (max-width: 800px) {
.tab-shell { grid-template-columns: 1fr; }
.tab-list { position: static; flex-direction: row; flex-wrap: wrap; }
.stages { grid-template-columns: 1fr; }
.stage:not(:last-child)::after { display: none; }
h1 { font-size: 32px; }
}
/* Layer cards (tabbed) */
.grid { display: contents; }
.layer-card {
display: none;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 14px;
padding: 22px 24px;
transition: border-color 160ms ease;
box-shadow: var(--shadow);
}
.layer-card.active {
display: block;
border-color: var(--blue);
}
.layer-card.active .layer-detail { display: block; }
.toggle { display: none; }
.layer-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
}
.layer-title {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.layer-badge {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 3px 8px;
border-radius: 999px;
background: var(--blue-soft);
color: var(--blue);
white-space: nowrap;
}
.layer-badge.amber { background: var(--amber-soft); color: var(--amber); }
.layer-badge.green { background: var(--green-soft); color: var(--green); }
.layer-badge.purple { background: var(--purple-soft); color: var(--purple); }
.layer-summary {
margin: 12px 0 0;
font-size: 15px;
color: var(--ink-soft);
}
.toggle {
margin-top: 14px;
font-size: 12px;
color: var(--blue);
font-weight: 600;
letter-spacing: 0.04em;
}
.layer-card.open .toggle::before { content: "Hide detail \2191"; }
.layer-card:not(.open) .toggle::before { content: "Show detail \2193"; }
.layer-detail {
display: none;
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid var(--line-soft);
font-size: 14.5px;
color: var(--ink);
}
.layer-card.open .layer-detail { display: block; }
.layer-detail h4 {
margin: 16px 0 6px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--ink-soft);
}
.layer-detail h4:first-child { margin-top: 0; }
.layer-detail ul {
margin: 0;
padding-left: 20px;
}
.layer-detail li { margin-bottom: 6px; }
.layer-detail code {
background: var(--panel-2);
border: 1px solid var(--line-soft);
border-radius: 4px;
padding: 1px 5px;
font-family: "SF Mono", Menlo, Consolas, monospace;
font-size: 12.5px;
}
/* Weight bar */
.weight-bar {
display: flex;
width: 100%;
height: 30px;
border-radius: 6px;
overflow: hidden;
margin: 8px 0 4px;
border: 1px solid var(--line);
}
.weight-seg {
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 11px;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.weight-legend {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 6px 14px;
margin-top: 10px;
font-size: 13px;
}
.weight-legend span { display: inline-flex; align-items: center; }
.weight-dot {
display: inline-block;
width: 10px; height: 10px; border-radius: 2px;
margin-right: 6px;
}
/* Footer */
footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--line);
color: var(--ink-soft);
font-size: 13px;
}
footer .grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
@media (max-width: 700px) {
footer .grid-2 { grid-template-columns: 1fr; }
}
.stat {
font-size: 22px;
font-weight: 700;
color: var(--ink);
}
/* === SHARED TOP NAV === */
.topnav {
position: sticky; top: 0; z-index: 50;
background: rgba(247, 245, 240, 0.92);
backdrop-filter: saturate(140%) blur(8px);
-webkit-backdrop-filter: saturate(140%) blur(8px);
border-bottom: 1px solid var(--line);
}
.topnav-inner {
max-width: 1280px; margin: 0 auto; padding: 11px 24px;
display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
}
.topnav-brand {
font-weight: 700; font-size: 15px; letter-spacing: 0.04em;
color: var(--ink); text-decoration: none;
}
.topnav-brand .dot { color: var(--blue); margin-right: 4px; }
.topnav ul {
display: flex; gap: 4px; list-style: none; padding: 0; margin: 0; flex-wrap: wrap;
}
.topnav a {
display: inline-block; padding: 6px 12px; border-radius: 999px;
font-size: 13px; color: var(--ink-soft); text-decoration: none;
border: 1px solid transparent; transition: background 120ms, color 120ms, border-color 120ms;
}
.topnav a:hover { color: var(--ink); border-color: var(--line); background: var(--panel); }
.topnav a.active { background: var(--blue); color: white; border-color: var(--blue); }
/* === /SHARED TOP NAV === */
</style>
</head>
<body>
<nav class="topnav" aria-label="Writ navigation">
<div class="topnav-inner">
<a class="topnav-brand" href="writ-complete-overview.html"><span class="dot">•</span>Writ</a>
<ul>
<li><a class="active" href="writ-architecture-flowchart.html">Architecture</a></li>
<li><a href="writ-workflow-flowchart.html">Workflow</a></li>
<li><a href="writ-commands.html">Commands</a></li>
</ul>
</div>
</nav>
<div class="wrap">
<header class="hero">
<div class="eyebrow">Writ Architecture</div>
<h1>A retrieval engine and a write-gate, working together.</h1>
<p class="pitch">
Writ sits between an AI coding agent and the file system. It pulls
<strong>the right coding rules</strong> from a knowledge graph in well
under a millisecond, then <strong>refuses to let the agent write code</strong>
until the user has approved a plan and the test skeletons. Two co-equal
layers: a hybrid-RAG knowledge service, and a session-aware enforcement
state machine.
</p>
</header>
<!-- 5-stage pipeline visual -->
<div class="pipeline" aria-label="Five-stage retrieval pipeline">
<div class="pipeline-header">
<div class="pipeline-title">Retrieval pipeline: query in, ranked rules out</div>
<div class="pipeline-sub">p95 latency well under 1 ms on a 276-rule corpus</div>
</div>
<div class="stages">
<div class="stage">
<div class="stage-n">Stage 1</div>
<div class="stage-name">Domain filter</div>
<div class="stage-impl">post-filter on indexes</div>
</div>
<div class="stage">
<div class="stage-n">Stage 2</div>
<div class="stage-name">BM25 keyword</div>
<div class="stage-impl">Tantivy</div>
</div>
<div class="stage">
<div class="stage-n">Stage 3</div>
<div class="stage-name">ANN vector</div>
<div class="stage-impl">hnswlib + MiniLM</div>
</div>
<div class="stage">
<div class="stage-n">Stage 4</div>
<div class="stage-name">Graph traversal</div>
<div class="stage-impl">Neo4j, cached</div>
</div>
<div class="stage">
<div class="stage-n">Stage 5</div>
<div class="stage-name">Two-pass rank</div>
<div class="stage-impl">RRF + weights</div>
</div>
</div>
</div>
<!-- Layer cards (tabbed) -->
<div class="tab-shell">
<nav class="tab-list" id="layer-tabs" aria-label="Architecture layers">
<button class="tab-btn active" data-target="layer-pipeline">1. Pipeline</button>
<button class="tab-btn" data-target="layer-weights">2. Weighting</button>
<button class="tab-btn" data-target="layer-split">3. Mandatory vs Retrieved</button>
<button class="tab-btn" data-target="layer-graph">4. Knowledge Graph</button>
<button class="tab-btn" data-target="layer-modes">5. Mode System</button>
<button class="tab-btn" data-target="layer-phases">6. Phase Gates</button>
<button class="tab-btn" data-target="layer-enforcement">7. Enforcement Layer</button>
<button class="tab-btn" data-target="layer-evolution">8. Evolution Model</button>
</nav>
<div class="grid" id="layers">
<!-- 1. Pipeline -->
<article class="layer-card" data-layer-id="layer-pipeline">
<div class="layer-head">
<h3 class="layer-title">1. The 5-Stage Retrieval Pipeline</h3>
<span class="layer-badge">retrieval</span>
</div>
<p class="layer-summary">
For every code-related prompt, Writ asks: "which rules matter for this exact task?"
and returns the answer in under a millisecond by running five small searches in sequence.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>Why this exists</h4>
<ul>
<li>A single retriever (keyword-only, or vector-only) misses rules a coding agent
actually needs. Combining BM25 + ANN + graph closes those gaps.</li>
<li>The whole thing must finish in roughly the same time as one Python function call,
so it can run on every prompt without slowing the agent down.</li>
</ul>
<h4>What each stage does</h4>
<ul>
<li><strong>Stage 1: Domain filter.</strong> Drops rules outside the active domain
(Python vs. Magento vs. infrastructure) before any ranking work. Source:
<code>writ/retrieval/pipeline.py:246-270</code>.</li>
<li><strong>Stage 2: BM25 keyword search.</strong> Tantivy index over rule
trigger (2.0x boost), statement, tags, and body (0.5x dilution). Returns up to
50 candidates. Source: <code>writ/retrieval/keyword.py:28-117</code>.</li>
<li><strong>Stage 3: ANN vector search.</strong> hnswlib over 384-dim embeddings
from <code>all-MiniLM-L6-v2</code> (ONNX preferred, SentenceTransformer fallback).
HNSW params <code>M=16</code>, <code>ef_construction=200</code>, <code>ef_search=50</code>.
Returns up to 10 candidates. Source: <code>writ/retrieval/embeddings.py</code>.</li>
<li><strong>Stage 4: Graph traversal.</strong> A pre-computed adjacency cache lets
us look up neighbors of any rule in constant time (no live Cypher per query).
Two-hop expansion. Source: <code>writ/retrieval/traversal.py:23-147</code>.</li>
<li><strong>Stage 5: Two-pass ranking with RRF.</strong> First pass picks the top
three highest-authority rules. Second pass scores everything else by proximity
to those three. Reciprocal Rank Fusion combines BM25 and vector signals before
weights are applied. Source: <code>writ/retrieval/ranking.py</code>.</li>
</ul>
<h4>One example</h4>
<ul>
<li>Prompt: "add a Magento product repository method that loads by SKU."
Stage 1 keeps only Magento + arch rules. Stage 2's BM25 hits on "repository,"
"factory," "loadBy." Stage 3's vector hits on the rationale text. Stage 4 pulls
in the related anti-pattern (<code>$factory->create()->load()</code>).
Stage 5 ranks them and the agent gets a small bundle of relevant rules.</li>
</ul>
</div>
</article>
<!-- 2. Weighting system -->
<article class="layer-card" data-layer-id="layer-weights">
<div class="layer-head">
<h3 class="layer-title">2. The Weighting System</h3>
<span class="layer-badge">retrieval</span>
</div>
<p class="layer-summary">
Five signals combine into the final rank. The weights are not guesses, they were
tuned against a ground-truth corpus.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>The exact weights</h4>
<div class="weight-bar" aria-label="Ranking weight breakdown">
<div class="weight-seg" style="background:#0a4a7f; flex:0.594" title="Vector 0.594">59.4%</div>
<div class="weight-seg" style="background:#1e7fc6; flex:0.198" title="BM25 0.198">19.8%</div>
<div class="weight-seg" style="background:#b45309; flex:0.099" title="Severity 0.099">9.9%</div>
<div class="weight-seg" style="background:#047857; flex:0.099" title="Confidence 0.099">9.9%</div>
<div class="weight-seg" style="background:#6d28d9; flex:0.01" title="Graph 0.01">1%</div>
</div>
<div class="weight-legend">
<span><span class="weight-dot" style="background:#0a4a7f"></span>Vector (ANN): <strong>0.594</strong></span>
<span><span class="weight-dot" style="background:#1e7fc6"></span>BM25 (keyword): <strong>0.198</strong></span>
<span><span class="weight-dot" style="background:#b45309"></span>Severity: <strong>0.099</strong></span>
<span><span class="weight-dot" style="background:#047857"></span>Confidence: <strong>0.099</strong></span>
<span><span class="weight-dot" style="background:#6d28d9"></span>Graph proximity: <strong>0.01</strong></span>
</div>
<h4>What each weight means</h4>
<ul>
<li><strong>Vector dominates</strong> because semantic matches recover rules whose
triggers do not share vocabulary with the query.</li>
<li><strong>BM25 anchors</strong> for exact-name lookups (rule IDs, identifiers).</li>
<li><strong>Severity</strong> bumps critical rules above advisory ones when scores tie.
Scale: critical 1.0, high 0.75, medium 0.5, low 0.25.</li>
<li><strong>Confidence</strong> reflects how trusted a rule is: battle-tested 1.0,
production-validated 0.8, peer-reviewed 0.6, speculative 0.3.</li>
<li><strong>Graph proximity</strong> is a tiebreaker: 1.0 for one-hop neighbors of
the top-three seed rules, 0.5 for two-hop, 0.0 otherwise.</li>
</ul>
<h4>Literal-query override</h4>
<ul>
<li>When the agent asks an exact-phrase question (looking up a specific rule by ID),
BM25 and vector both get 0.396, because semantic matching adds noise.</li>
<li>Frequency graduation: once a rule has 50 or more observations with a 75%+
positive ratio, the empirical ratio replaces the static confidence weight.
Source: <code>writ/frequency.py:28-53</code>.</li>
</ul>
<h4>Source</h4>
<ul>
<li><code>writ/retrieval/ranking.py:22-26</code> for the defaults.</li>
</ul>
</div>
</article>
<!-- 3. Mandatory vs retrieved split -->
<article class="layer-card" data-layer-id="layer-split">
<div class="layer-head">
<h3 class="layer-title">3. Mandatory vs. Retrieved: the Architectural Invariant</h3>
<span class="layer-badge amber">invariant</span>
</div>
<p class="layer-summary">
A small set of rules is injected on every prompt, no questions asked. The vast majority
of rules are retrieved on demand. This split is the most important design decision in Writ.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>Mandatory rules (always on)</h4>
<ul>
<li>Forbidden phrases, communication rules, and structural enforcement rules.</li>
<li>Capped at ~5K tokens per session to keep the budget predictable.</li>
<li>Never retrieved by BM25 or ANN. They are excluded at index-build time
and at query time (<code>writ/retrieval/pipeline.py:510</code>).</li>
<li>Loaded via the separate <code>GET /always-on</code> endpoint.</li>
</ul>
<h4>Retrieved rules (RAG-driven)</h4>
<ul>
<li>Every domain rule, skill, playbook, anti-pattern, and pressure scenario.</li>
<li>Pulled into context only when relevant to the current prompt.</li>
<li>Subject to the five-stage pipeline.</li>
<li>Deduplicated across turns so the same rule is not re-injected.</li>
</ul>
<h4>Why the split matters</h4>
<ul>
<li>The mandatory band is a permanent floor: rules so important they must apply to
<em>every</em> turn, even if the agent does not ask for them.</li>
<li>The retrieved band is variable: it lets us add 1000+ rules to the corpus without
paying for any of them on prompts that do not need them.</li>
<li>Together they let Writ scale without scaling the context budget.</li>
</ul>
</div>
</article>
<!-- 4. Knowledge graph -->
<article class="layer-card" data-layer-id="layer-graph">
<div class="layer-head">
<h3 class="layer-title">4. The Knowledge Graph</h3>
<span class="layer-badge">corpus</span>
</div>
<p class="layer-summary">
Rules are not a flat list. They live in a Neo4j graph with twelve node types and
more than ten edge types, so a rule can teach a skill, counter an anti-pattern,
or supersede an older rule, and the retrieval pipeline can follow those links.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>Twelve node types</h4>
<ul>
<li><strong>Rule</strong>: the primary enforceable rule.</li>
<li><strong>Abstraction</strong>: a compressed summary of a rule bundle.</li>
<li><strong>Skill</strong>: a teachable technique (retrievable).</li>
<li><strong>Playbook</strong>: a multi-phase workflow (retrievable).</li>
<li><strong>Technique</strong>: an implementation detail (retrievable).</li>
<li><strong>AntiPattern</strong>: a code smell to flag (retrievable).</li>
<li><strong>ForbiddenResponse</strong>: a prohibited AI output, always on.</li>
<li><strong>Phase</strong>: a step inside a playbook.</li>
<li><strong>Rationalization</strong>: a counter to a known excuse.</li>
<li><strong>PressureScenario</strong>: an adversarial test case.</li>
<li><strong>WorkedExample</strong>: a before/after illustration.</li>
<li><strong>SubagentRole</strong>: a role definition for an agentic worker.</li>
</ul>
<h4>Edge types</h4>
<ul>
<li><strong>Structural:</strong> <code>DEPENDS_ON</code>, <code>PRECEDES</code>,
<code>CONFLICTS_WITH</code>, <code>SUPPLEMENTS</code>, <code>SUPERSEDES</code>,
<code>RELATED_TO</code>, <code>APPLIES_TO</code>.</li>
<li><strong>Methodology:</strong> <code>TEACHES</code>, <code>COUNTERS</code>,
<code>DEMONSTRATES</code>, <code>DISPATCHES</code>, <code>GATES</code>,
<code>PRESSURE_TESTS</code>, <code>CONTAINS</code>, <code>ATTACHED_TO</code>.</li>
</ul>
<h4>How the graph helps retrieval</h4>
<ul>
<li>Hit one rule, get the bundle: the adjacency cache lets the pipeline expand a
single match into its directly related rules without a Cypher round-trip.</li>
<li>Conflicts and supersessions are first-class: the gate refuses to ingest a new
rule that conflicts with an existing one.</li>
<li>Source: <code>writ/graph/schema.py</code>.</li>
</ul>
</div>
</article>
<!-- 5. Mode system -->
<article class="layer-card" data-layer-id="layer-modes">
<div class="layer-head">
<h3 class="layer-title">5. The Mode System</h3>
<span class="layer-badge purple">enforcement</span>
</div>
<p class="layer-summary">
Every session declares a mode up front. The mode decides how much ceremony Writ
imposes: from "just answer" to "no code without an approved plan and tests."
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>Four modes</h4>
<ul>
<li><strong>Conversation:</strong> discussion, brainstorming, questions. No phase
gates. No write blocking.</li>
<li><strong>Debug:</strong> investigating a specific problem. Read-heavy, no code
generation expected.</li>
<li><strong>Review:</strong> evaluating existing code against rules. Structured
findings, no edits.</li>
<li><strong>Work:</strong> building or modifying code. Full workflow with phase
gates (see card 6).</li>
</ul>
<h4>How a mode is set</h4>
<ul>
<li>The agent runs <code>writ-session.py mode set <mode> <session_id></code>
at the start of the session.</li>
<li>If no mode is set, the gate hooks block all writes except <code>plan.md</code>.</li>
<li>The <code>--orchestrator</code> flag tells Writ to suppress broad RAG injection
in the master session because sub-agent workers will do their own retrieval.</li>
</ul>
<h4>Why modes exist</h4>
<ul>
<li>Most prompts are not "write me production code." Conversation and debug modes
give the agent permission to be useful without paying the cost of the full workflow.</li>
<li>Work mode is the strict mode: it is opt-in, and once entered it cannot be silently
bypassed.</li>
<li>Source: <code>bin/lib/writ-session.py:835-985</code>.</li>
</ul>
</div>
</article>
<!-- 6. Phase gates -->
<article class="layer-card" data-layer-id="layer-phases">
<div class="layer-head">
<h3 class="layer-title">6. The Phase Gates (Work Mode)</h3>
<span class="layer-badge purple">enforcement</span>
</div>
<p class="layer-summary">
Inside Work mode the agent moves through four gates. Each gate is a file the user has
to approve before the agent is allowed to write the next thing.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>The four phases</h4>
<ul>
<li><strong>Planning:</strong> the agent writes <code>plan.md</code> and
<code>capabilities.md</code> to the project root. Required sections:
Files, Analysis, Rules Applied, Capabilities.</li>
<li><strong>Testing:</strong> the agent writes test skeleton files with method signatures
and assertions. No implementation code is allowed yet.</li>
<li><strong>Implementation:</strong> the agent writes the production code that makes
the tests pass. Tests are run automatically after each write.</li>
<li><strong>Verification:</strong> the agent verifies the result and updates
<code>capabilities.md</code> with checked-off items.</li>
</ul>
<h4>How approval works</h4>
<ul>
<li>The user types "approved" after seeing the plan or the test skeletons.</li>
<li>That triggers the <code>/writ-approve</code> command, which calls
<code>POST /session/{id}/advance-phase</code> with <code>confirmation_source: "tool"</code>.</li>
<li>The audit trail records who approved what, when, and via which path
(tool vs. pattern match).</li>
<li>Pattern matching on "approved" is defense in depth only. The primary path is
the tool call.</li>
</ul>
<h4>Why this exists</h4>
<ul>
<li>Agents over-eager to write code is the most common failure mode in agentic
coding. Phase gates make the agent pause and show its work.</li>
<li>Each gate is a small contract the user can read in 30 seconds.</li>
</ul>
</div>
</article>
<!-- 7. Enforcement layer -->
<article class="layer-card" data-layer-id="layer-enforcement">
<div class="layer-head">
<h3 class="layer-title">7. The Enforcement Layer (Hooks)</h3>
<span class="layer-badge green">enforcement</span>
</div>
<p class="layer-summary">
Writ ships about thirty bash hooks that fire at well-defined moments in the
agent's lifecycle. They inject rules, validate writes, and block actions that would
violate the workflow.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>When hooks fire</h4>
<ul>
<li><strong>SessionStart:</strong> load mandatory rules, register the session.</li>
<li><strong>UserPromptSubmit:</strong> inject the always-active rules block and
run the RAG retrieval for the prompt.</li>
<li><strong>PreToolUse (Write, Edit, Bash, Read, ExitPlanMode, Task, TodoWrite):</strong>
validate that the action is allowed in the current phase, and that the file passes
structural checks.</li>
<li><strong>PostToolUse:</strong> validate the written file against rules, mark
pending tests, and inject any rules surfaced by the write itself.</li>
<li><strong>Stop / SessionEnd:</strong> run pending tests, log friction events,
audit the session.</li>
<li><strong>PreCompact / PostCompact:</strong> snapshot and restore state across
context compactions.</li>
</ul>
<h4>The gate system</h4>
<ul>
<li>Each phase has a gate file: <code>plan.md</code> for planning, the test skeletons
for testing, the implementation files for implementation.</li>
<li>A pre-write hook checks the gate state before allowing the write. If the
current phase forbids the action, the hook denies with an explicit reason.</li>
<li>Sub-agent workers are exempt from the gate: they run in their own session
with <code>is_subagent: true</code>, so they can do their assigned task without
the orchestrator's phase pressure.</li>
</ul>
<h4>How risky rule proposals are filtered</h4>
<ul>
<li>The "structural gate" runs five checks before any AI-proposed rule enters the graph:
schema validation, mechanical enforcement path (for mandatory rules), specificity
(rejecting vague language like "consider" or "try to"), redundancy (cosine similarity
against existing rules), and conflict (against CONFLICTS_WITH edges).</li>
<li>Source: <code>writ/gate.py:54-221</code>.</li>
</ul>
</div>
</article>
<!-- 8. Evolution model -->
<article class="layer-card" data-layer-id="layer-evolution">
<div class="layer-head">
<h3 class="layer-title">8. The Evolution Model</h3>
<span class="layer-badge amber">corpus</span>
</div>
<p class="layer-summary">
The corpus is not static. AI agents propose new rules, structural gates filter the
noise, and frequency tracking promotes rules that actually pay off in practice.
</p>
<div class="toggle"></div>
<div class="layer-detail">
<h4>The authority ladder</h4>
<ul>
<li><strong>human:</strong> the default for hand-authored rules.</li>
<li><strong>ai-provisional:</strong> the only authority an AI proposal can claim,
forced by <code>propose_rule</code>. Bound to <em>speculative</em> confidence
(weight 0.3).</li>
<li><strong>ai-promoted:</strong> set by <code>writ review --promote</code> after
a human reviewer accepts an AI-provisional rule. Confidence bumps to
<em>peer-reviewed</em> (weight 0.6).</li>
</ul>
<h4>The confidence ladder</h4>
<ul>
<li><strong>speculative</strong> (0.3): freshly proposed by AI.</li>
<li><strong>peer-reviewed</strong> (0.6): a human approved promotion.</li>
<li><strong>production-validated</strong> (0.8): static default for established rules.</li>
<li><strong>battle-tested</strong> (1.0): granted only by empirical frequency.</li>
</ul>
<h4>Frequency graduation</h4>
<ul>
<li>Every time a rule fires, the system records whether it stuck (the agent
followed it) or was rationalized away.</li>
<li>Once a rule reaches 50 observations with a 75%+ positive ratio, the empirical
ratio overrides the static confidence weight in ranking.</li>
<li>Source: <code>writ/frequency.py</code>.</li>
</ul>
<h4>The structural gate (one more time)</h4>
<ul>
<li>No AI proposal reaches the graph without passing all five gate checks. This is
how Writ keeps an AI from polluting its own corpus.</li>
</ul>
</div>
</article>
</div>
</div>
<footer>
<div class="grid-2">
<div>
<div class="eyebrow">By the numbers</div>
<div class="stat">5</div>
<div>retrieval stages, sub-millisecond p95 latency</div>
</div>
<div>
<div class="eyebrow">Enforcement</div>
<div class="stat">~30</div>
<div>bash hooks across 11 trigger types</div>
</div>
</div>
<p style="margin-top:24px">
Click any card above to drill in. All numbers and file paths in the detail panels
are sourced from the codebase at
<code style="background:transparent;border:none">~/.claude/skills/writ</code>.
</p>
</footer>
</div>
<script>
// Tab switching: only one layer card visible at a time.
var tabBtns = document.querySelectorAll('#layer-tabs .tab-btn');
var cards = document.querySelectorAll('.layer-card');
function selectLayer(layerId) {
tabBtns.forEach(function (b) {
b.classList.toggle('active', b.dataset.target === layerId);
});
cards.forEach(function (c) {
c.classList.toggle('active', c.dataset.layerId === layerId);
});
}
tabBtns.forEach(function (b) {
b.addEventListener('click', function () { selectLayer(b.dataset.target); });
});
// Default to the first layer on load.
if (cards.length) cards[0].classList.add('active');
</script>
</body>
</html>