@directive-run/claude-plugin 1.19.3 → 1.19.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directive-run/claude-plugin",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.4",
|
|
4
4
|
"description": "Claude Code plugin for Directive — 12 skills covering modules, constraints, resolvers, derivations, AI orchestration, and adapters. Installable via Claude Code's plugin marketplace or consumable programmatically as an npm package.",
|
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
|
6
6
|
"author": "Jason Comes",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"tsx": "^4.19.2",
|
|
53
53
|
"typescript": "^5.7.2",
|
|
54
54
|
"vitest": "^3.0.0",
|
|
55
|
-
"@directive-run/knowledge": "1.19.
|
|
55
|
+
"@directive-run/knowledge": "1.19.4"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsx scripts/build-skills.ts && tsup",
|
|
@@ -82,6 +82,30 @@ above), not a source. Wire it inside the orchestrator's constructor.
|
|
|
82
82
|
|
|
83
83
|
---
|
|
84
84
|
|
|
85
|
+
## What other agent frameworks have (and don't)
|
|
86
|
+
|
|
87
|
+
The closest comparable surfaces today, and what they cover:
|
|
88
|
+
|
|
89
|
+
| Capability | Directive `runStream({ liveContext })` | LangChain / LangGraph | Vercel AI SDK | LlamaIndex |
|
|
90
|
+
|---|---|---|---|---|
|
|
91
|
+
| **Mid-generation fact updates from an external stream** | ✅ source primitive bridges the transport; `liveContext` watches fact keys; predicate decides interrupt vs. notify | partial — `Tool` callbacks can be invoked but no "fact store the LLM sees through" surface | partial — `streamText` exposes `onChunk` for the consumer but no mid-stream re-prompt | partial — `QueryEngine` re-runs are caller-driven |
|
|
92
|
+
| **Declarative source / inbound subscription** | ✅ `source` primitive — engine owns mount/unmount; lifecycle observable | hand-rolled per integration (LangChain runnables don't own subscriptions) | n/a — Vercel AI is a streaming SDK, not a state engine | n/a — LlamaIndex is a retrieval framework |
|
|
93
|
+
| **Interrupt + resume against the same subscription** | ✅ `result.interrupt(reason?)` keeps the fact subscription alive; caller re-prompts | callback orchestration on the consumer | abort signal only — no "resume against the same context" affordance | re-query each time |
|
|
94
|
+
| **Tier 0 PII guard at the publish→fact boundary** | ✅ `createFactPIIGuardrail` — wired at `createSystem`; runs on every fact write | callback pattern; consumer wires per chain | input/output guardrails on the call; nothing at the fact-store / state-update boundary | Pydantic schema validation at the retrieval boundary |
|
|
95
|
+
| **Source × OTel out of the box** | ✅ `attachSourcesToOtel` — one long-lived span per `(sourceId, moduleId)` | manual `with_config(callbacks=...)` plumbing | consumer wires `onChunk` into their tracer | manual |
|
|
96
|
+
| **Multi-system reactive composition** | ✅ `liveContext.system` accepts ANY Directive system, even one not owned by the orchestrator | n/a — single-graph model | n/a | n/a |
|
|
97
|
+
|
|
98
|
+
Directive's pitch is not "we're a better LangChain" — it's **"your
|
|
99
|
+
state engine and your agent runtime share one fact store, so the agent
|
|
100
|
+
sees the world change without you wiring callbacks."** The shipped
|
|
101
|
+
1.x surface delivers that for inbound sources + abort-and-emit
|
|
102
|
+
interruption; the queued follow-up RFC adds automatic re-prompt with
|
|
103
|
+
merge strategies. See
|
|
104
|
+
[RFC 0005's "Demo" section](../../docs/rfcs/0005-live-context-agent.md#demo-the-launch-video)
|
|
105
|
+
for the 6-second launch GIF concept.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
85
109
|
## `runStream({ liveContext })` — Reactive Agents
|
|
86
110
|
|
|
87
111
|
> The killer demo: the agent is mid-generation, a source publishes a fact
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Choosing the right primitive
|
|
2
|
+
|
|
3
|
+
Directive ships six core primitives. Picking the wrong one — modeling
|
|
4
|
+
business state as a derivation, or running a side effect inside a
|
|
5
|
+
resolver — is the most common source of "Directive feels heavy"
|
|
6
|
+
complaints. The framework rewards getting this right; this page is the
|
|
7
|
+
decision matrix.
|
|
8
|
+
|
|
9
|
+
## The six primitives
|
|
10
|
+
|
|
11
|
+
| Primitive | Purpose | Reads | Writes | Triggers when |
|
|
12
|
+
|---|---|---|---|---|
|
|
13
|
+
| **`facts`** | Owned, mutable state | n/a | event handlers, `init`, resolvers, plugin pre-emit | the consumer writes to it |
|
|
14
|
+
| **`derivations`** | Pure, cached read-only views of facts | facts + other derivations | nothing | watched dependency changes |
|
|
15
|
+
| **`events`** | Typed mutation entry points | facts (via `f`) | facts (via `f`) | `system.events.name(payload)` or a source `publish` |
|
|
16
|
+
| **`constraints`** | Declarative "when X, require Y" rules | facts + derivations | NOTHING (emits requirements) | watched dependency changes |
|
|
17
|
+
| **`resolvers`** | Async work that fulfills a constraint's requirement | facts + derivations + `req.payload` | facts (via the resolver's `ctx.set`) | matched requirement enters the inflight queue |
|
|
18
|
+
| **`effects`** | Side effects that fire on fact change | facts + derivations | nothing inside Directive (writes to the outside world) | watched dependency changes |
|
|
19
|
+
| **`sources`** | External event streams mounted by the runtime | nothing | facts (via `publish` → event handler) | the external transport calls `publish(...)` |
|
|
20
|
+
|
|
21
|
+
## Decision tree
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Does this thing belong to my system's state?
|
|
25
|
+
├── No → it's external. Inbound or outbound?
|
|
26
|
+
│ ├── Inbound (something happens out there, I want to react)
|
|
27
|
+
│ │ ├── Once per ingest event (channel.on, addEventListener,
|
|
28
|
+
│ │ │ setInterval poll, MCP notification) → `source`
|
|
29
|
+
│ │ └── Continuous LLM token stream → AsyncIterable / runStream;
|
|
30
|
+
│ │ NOT a source (the source owns the connection; the
|
|
31
|
+
│ │ iterable owns the per-generation token sequence)
|
|
32
|
+
│ └── Outbound (state changed, I want to tell the world)
|
|
33
|
+
│ ├── Async work I want to retry / cancel / dedupe →
|
|
34
|
+
│ │ `resolver` (paired with a `constraint`)
|
|
35
|
+
│ └── Fire-and-forget side effect (log, ping, animate) →
|
|
36
|
+
│ `effect`
|
|
37
|
+
└── Yes → does it change over time?
|
|
38
|
+
├── Yes → `fact` (declare in `schema.facts`, mutate via
|
|
39
|
+
│ `events:` handlers and resolver `ctx.set`)
|
|
40
|
+
└── No → it's computed from other facts → `derivation`
|
|
41
|
+
(declare in `schema.derivations`, compute in `derive:`)
|
|
42
|
+
|
|
43
|
+
Then: does anything need to RUN when this changes?
|
|
44
|
+
├── A rule must trigger and require async work → `constraint`
|
|
45
|
+
│ (declarative `when`; emits a requirement; a `resolver` fulfills it)
|
|
46
|
+
├── A side effect that just runs → `effect`
|
|
47
|
+
└── Nothing — the LLM / hook / view just reads it next time → done
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Side-by-side comparisons
|
|
51
|
+
|
|
52
|
+
### `effect` vs `source`
|
|
53
|
+
|
|
54
|
+
The most common confusion. Both wrap something that fires "when
|
|
55
|
+
something changes."
|
|
56
|
+
|
|
57
|
+
| | `effect` | `source` |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Direction | **Outbound** — your state changes, code runs | **Inbound** — the world changes, your state updates |
|
|
60
|
+
| Trigger | watched facts / derivations change | external transport calls `publish` |
|
|
61
|
+
| Body | imperative side effect | call `publish(eventName, payload)` |
|
|
62
|
+
| Lifecycle | per fact change | mount-once at `start`, detach at `stop` |
|
|
63
|
+
| Owner | tied to a fact/derivation watcher | owns its external transport (channel, socket, listener) |
|
|
64
|
+
| Replaces | hand-rolled `useEffect(() => { /* writes elsewhere */ }, [factA])` | hand-rolled `useEffect(() => { subscribe(); return unsub; }, [])` |
|
|
65
|
+
|
|
66
|
+
If the `useEffect` body is `subscribe + return unsubscribe`, declare a
|
|
67
|
+
source. If the `useEffect` body is `do something external because some
|
|
68
|
+
fact changed`, declare an effect.
|
|
69
|
+
|
|
70
|
+
### `derivation` vs `resolver` vs `effect`
|
|
71
|
+
|
|
72
|
+
All three "react to fact changes." Pick by what they OWN:
|
|
73
|
+
|
|
74
|
+
| | `derivation` | `resolver` | `effect` |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| Owns a **value** | YES (the computed result) | NO | NO |
|
|
77
|
+
| Owns a **promise** | NO | YES (retries, cancels, dedupes) | NO (fire-and-forget) |
|
|
78
|
+
| Owns a **side effect** | NO (pure) | NO (writes facts via `ctx.set`) | YES |
|
|
79
|
+
| Reruns on input change | always (cached) | NEVER automatically — only when the constraint emits a new requirement | always |
|
|
80
|
+
| Can be `await`-ed externally | n/a (read synchronously) | YES (constraints + `system.settle()`) | NO |
|
|
81
|
+
| Wrong-choice symptom | "I want a fact but it's always derived" → fact; "I want side effects" → effect | "I want this to retry but it doesn't" → likely should be an effect; "I want this to run every render" → likely should be a derivation | "I want to write a fact based on the change" → likely should be a resolver |
|
|
82
|
+
|
|
83
|
+
### `event` vs `resolver`
|
|
84
|
+
|
|
85
|
+
Both can write facts. Pick by who calls them:
|
|
86
|
+
|
|
87
|
+
| | `event` | `resolver` |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| Caller | external code (`system.events.name(payload)`) OR a source's `publish` | the engine, when a constraint emits a matching requirement |
|
|
90
|
+
| Body | synchronous mutation via `f` accessor | async, can retry, can await |
|
|
91
|
+
| Best for | user actions, source-driven mutations, deterministic state transitions | async work whose result is a fact update |
|
|
92
|
+
|
|
93
|
+
A typical pattern: `event` ingests raw data, `derivation` computes a
|
|
94
|
+
"needs sync?" flag, `constraint` triggers when the flag is true,
|
|
95
|
+
`resolver` does the async work, on success writes the result back via
|
|
96
|
+
another `event`. The same fact pipeline doesn't mix `events` and
|
|
97
|
+
`resolvers` for the SAME write — events are user/source-driven; resolvers
|
|
98
|
+
are engine-driven.
|
|
99
|
+
|
|
100
|
+
### `constraint` vs `derivation` (predicates)
|
|
101
|
+
|
|
102
|
+
Both can hold `when` predicates. Different jobs:
|
|
103
|
+
|
|
104
|
+
- **`derivation`** evaluates an expression against the current facts
|
|
105
|
+
and returns a value. It's a noun. Other constraints / effects can
|
|
106
|
+
watch it.
|
|
107
|
+
- **`constraint`** evaluates a `when` predicate and, when it matches,
|
|
108
|
+
emits a `requirement` object that a resolver pattern-matches and
|
|
109
|
+
fulfills. It's a verb. Constraints never write — they describe what
|
|
110
|
+
the world MUST do.
|
|
111
|
+
|
|
112
|
+
The constraint's `when` may reference a derivation; that's the canonical
|
|
113
|
+
pattern (compute the condition cheaply once; multiple constraints share
|
|
114
|
+
it).
|
|
115
|
+
|
|
116
|
+
## Worked example
|
|
117
|
+
|
|
118
|
+
You're building a chat app that mirrors a Supabase realtime channel,
|
|
119
|
+
calls a moderation API on each message, and displays the result.
|
|
120
|
+
|
|
121
|
+
| Layer | Primitive | Why |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `messages: t.array<Message>()` | `fact` | mutable list owned by this module |
|
|
124
|
+
| `unmoderated: t.array<Id>()` | `derivation` | filtered view of `messages` where `verdict === null` |
|
|
125
|
+
| `realtime` (the broker subscription) | `source` | mount-once external event stream → `publish('MESSAGE_RECEIVED', row)` |
|
|
126
|
+
| `MESSAGE_RECEIVED` handler | `event` | source-driven mutation; writes the row into `messages` |
|
|
127
|
+
| `moderateUnverified` | `constraint` + `resolver` | constraint emits `MODERATE` requirements for each `unmoderated` id; resolver calls the moderation API + writes `MODERATION_COMPLETE` |
|
|
128
|
+
| `MODERATION_COMPLETE` handler | `event` | engine-driven mutation; sets `verdict` on the message |
|
|
129
|
+
| `announceFlag` | `effect` | watches `unmoderated`, posts to #alerts when count crosses a threshold |
|
|
130
|
+
| `mcpHealthCheck` | `source` | the moderation API has a status SSE; mount once, publish health changes |
|
|
131
|
+
|
|
132
|
+
Every external touch is a `source` or a `resolver`. Every state field
|
|
133
|
+
is a `fact` or a `derivation`. Every "must-happen rule" is a
|
|
134
|
+
`constraint`. Every "do this when X" is an `effect`. Six primitives,
|
|
135
|
+
seven pieces of work, zero `useEffect` hooks.
|
|
136
|
+
|
|
137
|
+
## When you find yourself wanting to break the model
|
|
138
|
+
|
|
139
|
+
- "I want to call `set` from inside a derivation" — you wanted a
|
|
140
|
+
resolver (the work has a result that's a fact update).
|
|
141
|
+
- "I want my effect to retry" — you wanted a resolver (resolvers
|
|
142
|
+
have retry config; effects don't).
|
|
143
|
+
- "I want my source to write multiple facts at once" — declare an
|
|
144
|
+
event whose handler does the batch write; the source publishes the
|
|
145
|
+
event with the batch payload.
|
|
146
|
+
- "I want to write a fact from a constraint" — you wanted a
|
|
147
|
+
resolver. Constraints declare; resolvers do.
|
|
148
|
+
- "I have a value that's derived from outside state (URL, env, time)"
|
|
149
|
+
— declare it as a `fact` whose `init` reads the external value,
|
|
150
|
+
and use a `source` if it changes (e.g. `popstate` listener).
|
|
151
|
+
|
|
152
|
+
## Related
|
|
153
|
+
|
|
154
|
+
- [`core-patterns.md`](./core-patterns.md) — module composition and
|
|
155
|
+
the recommended structure of a module file.
|
|
156
|
+
- [`sources.md`](./sources.md) — the source primitive's full lifecycle
|
|
157
|
+
and recipes.
|
|
158
|
+
- [`constraints.md`](./constraints.md) — `when` predicates, requirement
|
|
159
|
+
shapes, and the `bind` / `owns` field-scoped CAS contract.
|
|
160
|
+
- [`resolvers.md`](./resolvers.md) — retry config, batch semantics, and
|
|
161
|
+
the `ctx.set` write contract.
|
|
162
|
+
- [`anti-patterns.md`](./anti-patterns.md) — the things to avoid that
|
|
163
|
+
every Directive consumer eventually tries.
|