Your first crystallized pattern
Do the same thing three times, watch the pattern detector form a hash chain, see the trust score evolve, and observe automatic routing kick in. The substrate's cost curve bends here.
What you'll see
In about 30 minutes, you'll create three structurally similar threads, observe the pattern detector record their hash signatures at four levels, and verify that the next similar work matches the pattern and replays its outcome instead of triggering a fresh LLM call.
After this, you'll understand the economic claim behind Syncropel: the marginal cost of a coordination event approaches zero because most events are replays of crystallized patterns, not fresh reasoning.
Before you start
spl 0.33.0or later (spl --version)- A daemon running with
spl statushealthy. - Completed Your first AITL approval — patterns build on top of approved rules.
- Patience for ~3 emissions and the engine's tick interval. The pattern detector is part of the TICK loop; defaults run every 60 seconds.
This tutorial uses observability via spl thread records, spl trust, and curl http://localhost:9100/v1/patterns (the HTTP surface — spl pattern CLI is on the polish backlog per the patterns concept doc).
1. Pick a thread shape and emit three of them
Three structurally similar threads — same INTEND shape, same DO shape, same closing KNOW. A small, deterministic example: counting lines in three files.
# Thread 1
THREAD1=th_count_lines_a
spl intend "Count the lines in src/main.rs" --thread $THREAD1
spl do "Read file and count newlines" --thread $THREAD1
spl know "src/main.rs has 142 lines" --thread $THREAD1 --fulfills
# Thread 2
THREAD2=th_count_lines_b
spl intend "Count the lines in src/lib.rs" --thread $THREAD2
spl do "Read file and count newlines" --thread $THREAD2
spl know "src/lib.rs has 89 lines" --thread $THREAD2 --fulfills
# Thread 3
THREAD3=th_count_lines_c
spl intend "Count the lines in src/main.rs" --thread $THREAD3
spl do "Read file and count newlines" --thread $THREAD3
spl know "src/main.rs has 142 lines" --thread $THREAD3 --fulfillsEach --fulfills on the closing KNOW marks the thread as successfully closed — that's the trigger for pattern indexing.
2. Inspect the hash signatures
Each closed thread is now in the pattern index at four levels:
spl thread show $THREAD1You'll see the standard projection plus a pattern_hashes block:
Thread: th_count_lines_a (closed, fulfilled)
Records: 3 (INTEND → DO → KNOW)
Pattern hashes:
L0: 7f3a4c2e9b1d8a6f5c4b3a2e1d0c9b8a7f6e5d4c3b2a1908f7e6d5c4b3a2918
L1: 8c9d0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f
L2: a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
L3: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3Now compare to thread 2:
spl thread show $THREAD2Threads 1 and 2 should match at L1, L2, and L3 — same shape, same operation, same flow boundary. They differ at L0 (the file paths and line counts are different). This is the structural-vs-content split (per F8 in the glossary) that makes pattern sharing possible without leaking content.
Thread 1 and thread 3 should match at L0 — same exact INTEND body, same DO, same KNOW result. That's the strict-cache case.
3. Read the pattern index directly
Until spl pattern list ships, the pattern index is a substrate-shaped record query. Threads 1+3 are L0-identical; threads 1+2+3 are L1-equivalent. Inspect via the records HTTP API:
curl -s -H "Authorization: Bearer $(cat ~/.syncro/token)" \
"http://localhost:9100/v1/records?body.kind=core.pattern.observation.v1&limit=10" \
| jq '.records[] | {hash_l1: .body.hash_l1, observations: .body.observation_count}'You should see one entry whose hash_l1 matches all three threads, with observation_count: 3. Each successful --fulfills adds one observation.
4. Watch the trust score evolve
Each closed thread is also a trust observation in the actor's domain (defaults to general if you didn't pass --domain). Look at your trust:
spl trustACTOR DOMAIN SUCCESS TOTAL TRUST
did:sync:user:you general 3 3 0.439Three out of three successes. Trust uses Wilson lower-bound — a small sample size always has high uncertainty, so 3/3 ≠ 1.0; it ≈ 0.44. With more observations the score will climb.
Trust is one of the four crystallization criteria:
| Criterion | What it measures | Threshold |
|---|---|---|
| C1 Trust | Producing actor's trust in the domain | ≥ 0.90 |
| C2 Observations | Times this pattern has been seen | ≥ 100 |
| C3 Compression | MDL compression vs. listing observations | meaningful |
| C4 Multi-source | Distinct (actor, evaluator) pairs | ≥ 2 |
This tutorial demonstrates the observation phase, not full crystallization. Three observations from one actor doesn't crystallize. But — and this is the point — the pattern is queryable and the next similar record will match even before crystallization. Replay short-circuits at L1 today (see patterns concept § what's enforced).
5. Trigger a fourth record and watch automatic match
Emit one more line-count thread:
THREAD4=th_count_lines_d
spl intend "Count the lines in src/test.rs" --thread $THREAD4Inspect what the engine did with the bare INTEND:
spl thread records $THREAD4If routing has any rules that match the L1 signature of the closed pattern, you should see follow-on engine-emitted records — e.g., a DO with body.dispatch_handled: true and body.replay_source: <pattern_id>. Those engine-emitted records carry the pattern as evidence: state didn't come from new reasoning, it came from a structural-hash hit.
If no rules exist yet for this shape, the kernel will treat the new INTEND like the unrouted record from the AITL tutorial — proposing a routing rule on th_aitl. The pattern is observable but no auto-routing happens until a rule covers the shape.
6. Inspect the routing fold
The engine's runtime config — what rules will fire next — is the fold over th_engine_config:
spl thread records th_engine_config | jq '.[] | select(.body.kind == "core.routing_rule.v1") | {name: .body.name, match: .body.match}'Each entry is a LEARN record describing one rule. The fold-over-records is the routing table. There is no separate routing config file. This is what "state = fold(records)" looks like in production code.
What just happened
You exercised the substrate's pattern recognition primitive:
- Closed threads are pattern observations. Every
--fulfillsKNOW closes a thread and emits hash signatures at L0, L1, L2, L3 into the pattern index. This is automatic and free; the index is just records. - Hash levels separate structure from content. L0 hashes leak content (so they stay namespace-local per F8). L1-L3 are structural and safe to federate. This is what enables cross-team pattern sharing without leaking sensitive bodies.
- The cascade favors cheap replays. When new work arrives, the engine matches L1 → L2 → L3 → semantic → CREATE. Each level is faster + cheaper than the next. Crystallized patterns short-circuit the whole cascade.
- Trust gates promotion. Three observations don't crystallize because C1 (trust ≥ 0.90) and C2 (observations ≥ 100) aren't met yet. Crystallization is what graduates a pattern from "observed" to "trusted enough to replay without re-checking."
- De-crystallization is real. If trust drops below 0.80 (per §07 trust), the pattern un-crystallizes. The system self-corrects rather than replaying into a wall.
The economic claim: at steady state, ~85% of incoming work matches a crystallized pattern and replays at near-zero marginal cost. The remaining 15% (the EXPLORE/CREATE tail) is where operational LLM spend lives. As more patterns crystallize, that fraction shrinks. This is what makes the substrate viable at scale.
Where to next
- Patterns concept — the full model: hash levels, the cascade, crystallization criteria C1-C4, de-crystallization, federation.
- Trust concept — Wilson lower-bound math, 90-day decay, CUSUM drift detection, why trust gates pattern promotion.
- Engine concept — where pattern matching slots into the four loops (RECONCILE checks the cascade on every routing decision).
- Your first federation pair — once patterns crystallize on one steward, they can federate to paired peers (subject to consent + hash-level filter).
- Cookbook: Trust-gated auto-approval — composing trust + AITL into automatic decisions for high-trust actors.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Hash signatures don't appear in spl thread show | The thread isn't closed. Pattern indexing fires only when a closing KNOW carries --fulfills against a prior INTEND. | Re-emit the closing KNOW with --fulfills (or --fulfills <intend_record_id> if you want to be explicit). Without it, the thread is "open" and the pattern index doesn't pick it up. |
| Threads 1 and 2 match at L0 but not L1 | Mis-emitted bodies — likely identical body strings. L0 hashes the bytes; identical bytes → L0 match. | This is fine, just less interesting. Vary the INTEND text between threads (different filename, different parameter) to demonstrate the L0 vs L1 split. |
| Pattern observation count stuck at 1 | Each thread closed but they hash differently at every level (different body kinds, different actor, different acts). | Verify all three threads use the same act sequence (INTEND → DO → KNOW), the same actor, and structurally similar body shapes. Use spl thread show <id> to compare hash blocks side by side. |
Trust score flat at 0.0 after --fulfills | The closing KNOW didn't include a --domain — observations land in general and your spl trust view may be filtered. | Run spl trust with no flags to see all domains. Or pass --domain code (or whatever fits) to spl know to attribute observations to a specific domain. |
| Fourth record didn't auto-route | No routing rule covers the pattern's L1 signature yet. Patterns make routing possible; rules make it fire. | Either approve an AITL proposal (per the AITL tutorial) or hand-author a rule with spl config add-rule. Patterns and rules are orthogonal — the cascade matches patterns; rules dispatch. |
Your first AITL approval
Walk through the substrate's self-improvement loop. Emit a record that has no routing rule yet, see the kernel propose one, approve it, and watch the next similar record route automatically.
Your first federation pair
Pair two daemons in one command. Emit on one, query on the other, see federated records flow consent-gated. The smallest two-instance walkthrough.