flowscript-agents 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/PKG-INFO +102 -14
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/README.md +101 -13
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/docs/audit-trail.md +13 -2
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/examples/CLAUDE.md.example +2 -1
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/audit.py +15 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/camel_ai.py +10 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/crewai.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/extract.py +68 -2
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/google_adk.py +10 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/haystack.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/langgraph.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/llamaindex.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/mcp.py +134 -6
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/memory.py +19 -2
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/openai_agents.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/pydantic_ai.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/smolagents.py +9 -3
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/pyproject.toml +1 -1
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_mcp.py +3 -2
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/.gitignore +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/AUDIT_TRAIL_DESIGN.md +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/docs/adapters.md +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/docs/api-reference.md +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/docs/flowscript-demo.png +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/docs/lifecycle.md +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/examples/langgraph_live_test.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/examples/temporal_e2e_test.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/__init__.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/__init__.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/_utils.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/consolidate.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/index.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/providers.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/embeddings/search.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/query.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/types.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/flowscript_agents/unified.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/scripts/validate_dedup_threshold.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/conftest.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_audit.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_camel_ai.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_consolidation.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_crewai.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_embeddings.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_google_adk.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_haystack.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_langgraph.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_llamaindex.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_memory.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_openai_agents.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_pydantic_ai.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_smolagents.py +0 -0
- {flowscript_agents-0.2.0 → flowscript_agents-0.2.2}/tests/test_temporal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowscript-agents
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Complete agent memory: reasoning queries + vector search + auto-extraction. Decision intelligence for LangGraph, CrewAI, Google ADK, OpenAI Agents SDK, Pydantic AI, smolagents, LlamaIndex, Haystack, CAMEL-AI, and Vercel AI SDK.
|
|
5
5
|
Project-URL: Homepage, https://flowscript.org
|
|
6
6
|
Project-URL: Repository, https://github.com/phillipclapham/flowscript-agents
|
|
@@ -83,19 +83,20 @@ llm = lambda prompt: (client.chat.completions.create(
|
|
|
83
83
|
).choices[0].message.content or "")
|
|
84
84
|
|
|
85
85
|
with UnifiedMemory("agent-memory.json", embedder=OpenAIEmbeddings(), llm=llm) as mem:
|
|
86
|
-
mem.add("
|
|
87
|
-
mem.add("Redis
|
|
88
|
-
mem.add("
|
|
86
|
+
mem.add("Redis gives sub-ms reads which is critical for our UX requirements")
|
|
87
|
+
mem.add("Redis clustering costs $200/month which exceeds our infrastructure budget of $50/month")
|
|
88
|
+
mem.add("PostgreSQL gives us rich queries at $15/month but read latency is 10-50ms")
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
# →
|
|
92
|
-
#
|
|
90
|
+
tensions = mem.memory.query.tensions()
|
|
91
|
+
# → TensionsResult(1 tension, axes=['cost vs budget'])
|
|
92
|
+
# The LLM detected the $200/month vs $50/month contradiction
|
|
93
|
+
# and preserved both sides as a queryable tension
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
# →
|
|
95
|
+
blocked = mem.memory.query.blocked()
|
|
96
|
+
# → BlockedResult(0 blockers)
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
# → full
|
|
98
|
+
why = mem.memory.query.why(node_id)
|
|
99
|
+
# → CausalAncestry: full chain backward from any node
|
|
99
100
|
```
|
|
100
101
|
|
|
101
102
|
Five queries that no vector store can answer — `why()`, `tensions()`, `blocked()`, `alternatives()`, `whatIf()` — over a typed semantic graph. Drop-in adapters for [9 agent frameworks](#works-with-your-stack). Hash-chained audit trail. And when memories contradict, we don't delete the old one — we create a queryable *tension*.
|
|
@@ -110,6 +111,16 @@ Five queries that no vector store can answer — `why()`, `tensions()`, `blocked
|
|
|
110
111
|
|
|
111
112
|
### MCP Server (Claude Code / Cursor — zero code)
|
|
112
113
|
|
|
114
|
+
```bash
|
|
115
|
+
pip install flowscript-agents openai
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The `openai` package is required for extraction, consolidation, and vector search. Without it, `add_memory` stores raw text and `query_tensions` won't find anything.
|
|
119
|
+
|
|
120
|
+
Add to your editor's MCP config:
|
|
121
|
+
|
|
122
|
+
**Claude Code** — add to `.claude/settings.json` in your project (or `~/.claude/settings.json` for global):
|
|
123
|
+
|
|
113
124
|
```json
|
|
114
125
|
{
|
|
115
126
|
"mcpServers": {
|
|
@@ -124,11 +135,63 @@ Five queries that no vector store can answer — `why()`, `tensions()`, `blocked
|
|
|
124
135
|
}
|
|
125
136
|
```
|
|
126
137
|
|
|
138
|
+
**Cursor / Windsurf / VS Code** — add to `.mcp.json` in your project root:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mcpServers": {
|
|
143
|
+
"flowscript": {
|
|
144
|
+
"type": "stdio",
|
|
145
|
+
"command": "flowscript-mcp",
|
|
146
|
+
"args": ["--memory", "./project-memory.json"],
|
|
147
|
+
"env": {
|
|
148
|
+
"OPENAI_API_KEY": "your-key"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Fallback:** If `env` passthrough doesn't work in your editor, export the key in your shell before launching:
|
|
127
156
|
```bash
|
|
128
|
-
|
|
157
|
+
export OPENAI_API_KEY=your-key
|
|
129
158
|
```
|
|
130
159
|
|
|
131
|
-
|
|
160
|
+
The server auto-detects your API key and configures the full stack:
|
|
161
|
+
|
|
162
|
+
| Key | What you get |
|
|
163
|
+
|:----|:-------------|
|
|
164
|
+
| `OPENAI_API_KEY` | Vector search (text-embedding-3-small) + typed extraction (gpt-4o-mini) + consolidation |
|
|
165
|
+
| `ANTHROPIC_API_KEY` | Typed extraction + consolidation (no embeddings, keyword search fallback) |
|
|
166
|
+
| Neither | Raw text storage only. Tools work, but no typed extraction and `query_tensions` won't find anything. |
|
|
167
|
+
|
|
168
|
+
**Without an API key, you get a degraded experience.** The server warns on startup and in tool responses.
|
|
169
|
+
|
|
170
|
+
### Embedding Providers
|
|
171
|
+
|
|
172
|
+
The default is OpenAI `text-embedding-3-small`. To use a different provider, pass flags in `args`:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
"args": ["--memory", "./project-memory.json", "--embedder", "ollama", "--embedding-model", "nomic-embed-text"]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| Flag | What it does | Default |
|
|
179
|
+
|:-----|:-------------|:--------|
|
|
180
|
+
| `--embedder` | Embedding provider: `openai`, `sentence-transformers`, or `ollama` | Auto-detected from API key |
|
|
181
|
+
| `--embedding-model` | Model name (provider-specific) | `text-embedding-3-small` (OpenAI) |
|
|
182
|
+
| `--llm-model` | LLM for extraction and consolidation | `gpt-4o-mini` |
|
|
183
|
+
| `--no-auto` | Disable auto-configuration from API keys | Off |
|
|
184
|
+
|
|
185
|
+
**Local embeddings (free, no API key for embeddings):**
|
|
186
|
+
|
|
187
|
+
| Provider | Install | Example model | Notes |
|
|
188
|
+
|:---------|:--------|:--------------|:------|
|
|
189
|
+
| Ollama | [Install Ollama](https://ollama.com), then `ollama pull nomic-embed-text` | `nomic-embed-text` | Beats text-embedding-3-small. 274MB. |
|
|
190
|
+
| SentenceTransformers | `pip install sentence-transformers` | `BAAI/bge-m3` | Runs on CPU. Downloads on first use. |
|
|
191
|
+
|
|
192
|
+
You still need an LLM API key (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) for typed extraction and consolidation, even when using local embeddings.
|
|
193
|
+
|
|
194
|
+
**Then add the [CLAUDE.md snippet](examples/CLAUDE.md.example) to your project.** This is what turns tools into a workflow. It tells your agent *when* to record decisions, surface tensions before new choices, and check blockers at session start. Without it, the tools are available but passive. With it, your agent proactively tracks your project's reasoning.
|
|
132
195
|
|
|
133
196
|
### Python SDK
|
|
134
197
|
|
|
@@ -155,6 +218,31 @@ FlowScript operates at three levels. Pick where you start:
|
|
|
155
218
|
|
|
156
219
|
---
|
|
157
220
|
|
|
221
|
+
## First 5 Minutes
|
|
222
|
+
|
|
223
|
+
With the MCP server running and the CLAUDE.md snippet in your project, try this conversation:
|
|
224
|
+
|
|
225
|
+
> "I need to decide between PostgreSQL and MongoDB for our user data. We need ACID compliance for payments but flexibility for user profiles."
|
|
226
|
+
|
|
227
|
+
Your agent stores the decision context, tradeoffs, and rationale automatically. Now introduce contradictory information:
|
|
228
|
+
|
|
229
|
+
> "Actually, I've been looking at DynamoDB. The scale requirements might matter more than I thought."
|
|
230
|
+
|
|
231
|
+
Now ask:
|
|
232
|
+
|
|
233
|
+
> "What tensions do we have in our architecture decisions?"
|
|
234
|
+
|
|
235
|
+
FlowScript preserved both perspectives (PostgreSQL's ACID compliance vs DynamoDB's scalability) as a queryable tension instead of deleting the first decision. That's what **RELATE > DELETE** means in practice.
|
|
236
|
+
|
|
237
|
+
After a few sessions, try:
|
|
238
|
+
- *"What's blocking our progress?"* surfaces blockers and their downstream impact
|
|
239
|
+
- *"Why did we choose PostgreSQL originally?"* traces the full causal chain
|
|
240
|
+
- *"What if we switch to DynamoDB?"* maps the downstream consequences
|
|
241
|
+
|
|
242
|
+
After 20 sessions, you have a curated knowledge base of your project's decisions, not a pile of notes. Knowledge that stays relevant graduates through temporal tiers. One-off observations fade naturally.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
158
246
|
## Works With Your Stack
|
|
159
247
|
|
|
160
248
|
Drop-in adapters that implement your framework's native interface. Same API you already use — plus `query.tensions()`.
|
|
@@ -261,7 +349,7 @@ Under the hood: a local semantic graph with typed nodes, typed relationships, an
|
|
|
261
349
|
|
|
262
350
|
| Package | What | Install |
|
|
263
351
|
|:--------|:-----|:--------|
|
|
264
|
-
| [flowscript-agents](https://pypi.org/project/flowscript-agents/) | Python SDK — 9 adapters, unified memory, consolidation, audit trail | `pip install flowscript-agents` |
|
|
352
|
+
| [flowscript-agents](https://pypi.org/project/flowscript-agents/) | Python SDK — 9 adapters, unified memory, consolidation, audit trail | `pip install flowscript-agents openai` |
|
|
265
353
|
| [flowscript-core](https://www.npmjs.com/package/flowscript-core) | TypeScript SDK — Memory class, 15 tools, token budgeting, audit trail | `npm install flowscript-core` |
|
|
266
354
|
| [flowscript.org](https://flowscript.org) | Web editor, D3 visualization, live query panel | Browser |
|
|
267
355
|
|
|
@@ -19,19 +19,20 @@ llm = lambda prompt: (client.chat.completions.create(
|
|
|
19
19
|
).choices[0].message.content or "")
|
|
20
20
|
|
|
21
21
|
with UnifiedMemory("agent-memory.json", embedder=OpenAIEmbeddings(), llm=llm) as mem:
|
|
22
|
-
mem.add("
|
|
23
|
-
mem.add("Redis
|
|
24
|
-
mem.add("
|
|
22
|
+
mem.add("Redis gives sub-ms reads which is critical for our UX requirements")
|
|
23
|
+
mem.add("Redis clustering costs $200/month which exceeds our infrastructure budget of $50/month")
|
|
24
|
+
mem.add("PostgreSQL gives us rich queries at $15/month but read latency is 10-50ms")
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
# →
|
|
28
|
-
#
|
|
26
|
+
tensions = mem.memory.query.tensions()
|
|
27
|
+
# → TensionsResult(1 tension, axes=['cost vs budget'])
|
|
28
|
+
# The LLM detected the $200/month vs $50/month contradiction
|
|
29
|
+
# and preserved both sides as a queryable tension
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
# →
|
|
31
|
+
blocked = mem.memory.query.blocked()
|
|
32
|
+
# → BlockedResult(0 blockers)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
# → full
|
|
34
|
+
why = mem.memory.query.why(node_id)
|
|
35
|
+
# → CausalAncestry: full chain backward from any node
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
Five queries that no vector store can answer — `why()`, `tensions()`, `blocked()`, `alternatives()`, `whatIf()` — over a typed semantic graph. Drop-in adapters for [9 agent frameworks](#works-with-your-stack). Hash-chained audit trail. And when memories contradict, we don't delete the old one — we create a queryable *tension*.
|
|
@@ -46,6 +47,16 @@ Five queries that no vector store can answer — `why()`, `tensions()`, `blocked
|
|
|
46
47
|
|
|
47
48
|
### MCP Server (Claude Code / Cursor — zero code)
|
|
48
49
|
|
|
50
|
+
```bash
|
|
51
|
+
pip install flowscript-agents openai
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `openai` package is required for extraction, consolidation, and vector search. Without it, `add_memory` stores raw text and `query_tensions` won't find anything.
|
|
55
|
+
|
|
56
|
+
Add to your editor's MCP config:
|
|
57
|
+
|
|
58
|
+
**Claude Code** — add to `.claude/settings.json` in your project (or `~/.claude/settings.json` for global):
|
|
59
|
+
|
|
49
60
|
```json
|
|
50
61
|
{
|
|
51
62
|
"mcpServers": {
|
|
@@ -60,11 +71,63 @@ Five queries that no vector store can answer — `why()`, `tensions()`, `blocked
|
|
|
60
71
|
}
|
|
61
72
|
```
|
|
62
73
|
|
|
74
|
+
**Cursor / Windsurf / VS Code** — add to `.mcp.json` in your project root:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"flowscript": {
|
|
80
|
+
"type": "stdio",
|
|
81
|
+
"command": "flowscript-mcp",
|
|
82
|
+
"args": ["--memory", "./project-memory.json"],
|
|
83
|
+
"env": {
|
|
84
|
+
"OPENAI_API_KEY": "your-key"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Fallback:** If `env` passthrough doesn't work in your editor, export the key in your shell before launching:
|
|
63
92
|
```bash
|
|
64
|
-
|
|
93
|
+
export OPENAI_API_KEY=your-key
|
|
65
94
|
```
|
|
66
95
|
|
|
67
|
-
|
|
96
|
+
The server auto-detects your API key and configures the full stack:
|
|
97
|
+
|
|
98
|
+
| Key | What you get |
|
|
99
|
+
|:----|:-------------|
|
|
100
|
+
| `OPENAI_API_KEY` | Vector search (text-embedding-3-small) + typed extraction (gpt-4o-mini) + consolidation |
|
|
101
|
+
| `ANTHROPIC_API_KEY` | Typed extraction + consolidation (no embeddings, keyword search fallback) |
|
|
102
|
+
| Neither | Raw text storage only. Tools work, but no typed extraction and `query_tensions` won't find anything. |
|
|
103
|
+
|
|
104
|
+
**Without an API key, you get a degraded experience.** The server warns on startup and in tool responses.
|
|
105
|
+
|
|
106
|
+
### Embedding Providers
|
|
107
|
+
|
|
108
|
+
The default is OpenAI `text-embedding-3-small`. To use a different provider, pass flags in `args`:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
"args": ["--memory", "./project-memory.json", "--embedder", "ollama", "--embedding-model", "nomic-embed-text"]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Flag | What it does | Default |
|
|
115
|
+
|:-----|:-------------|:--------|
|
|
116
|
+
| `--embedder` | Embedding provider: `openai`, `sentence-transformers`, or `ollama` | Auto-detected from API key |
|
|
117
|
+
| `--embedding-model` | Model name (provider-specific) | `text-embedding-3-small` (OpenAI) |
|
|
118
|
+
| `--llm-model` | LLM for extraction and consolidation | `gpt-4o-mini` |
|
|
119
|
+
| `--no-auto` | Disable auto-configuration from API keys | Off |
|
|
120
|
+
|
|
121
|
+
**Local embeddings (free, no API key for embeddings):**
|
|
122
|
+
|
|
123
|
+
| Provider | Install | Example model | Notes |
|
|
124
|
+
|:---------|:--------|:--------------|:------|
|
|
125
|
+
| Ollama | [Install Ollama](https://ollama.com), then `ollama pull nomic-embed-text` | `nomic-embed-text` | Beats text-embedding-3-small. 274MB. |
|
|
126
|
+
| SentenceTransformers | `pip install sentence-transformers` | `BAAI/bge-m3` | Runs on CPU. Downloads on first use. |
|
|
127
|
+
|
|
128
|
+
You still need an LLM API key (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`) for typed extraction and consolidation, even when using local embeddings.
|
|
129
|
+
|
|
130
|
+
**Then add the [CLAUDE.md snippet](examples/CLAUDE.md.example) to your project.** This is what turns tools into a workflow. It tells your agent *when* to record decisions, surface tensions before new choices, and check blockers at session start. Without it, the tools are available but passive. With it, your agent proactively tracks your project's reasoning.
|
|
68
131
|
|
|
69
132
|
### Python SDK
|
|
70
133
|
|
|
@@ -91,6 +154,31 @@ FlowScript operates at three levels. Pick where you start:
|
|
|
91
154
|
|
|
92
155
|
---
|
|
93
156
|
|
|
157
|
+
## First 5 Minutes
|
|
158
|
+
|
|
159
|
+
With the MCP server running and the CLAUDE.md snippet in your project, try this conversation:
|
|
160
|
+
|
|
161
|
+
> "I need to decide between PostgreSQL and MongoDB for our user data. We need ACID compliance for payments but flexibility for user profiles."
|
|
162
|
+
|
|
163
|
+
Your agent stores the decision context, tradeoffs, and rationale automatically. Now introduce contradictory information:
|
|
164
|
+
|
|
165
|
+
> "Actually, I've been looking at DynamoDB. The scale requirements might matter more than I thought."
|
|
166
|
+
|
|
167
|
+
Now ask:
|
|
168
|
+
|
|
169
|
+
> "What tensions do we have in our architecture decisions?"
|
|
170
|
+
|
|
171
|
+
FlowScript preserved both perspectives (PostgreSQL's ACID compliance vs DynamoDB's scalability) as a queryable tension instead of deleting the first decision. That's what **RELATE > DELETE** means in practice.
|
|
172
|
+
|
|
173
|
+
After a few sessions, try:
|
|
174
|
+
- *"What's blocking our progress?"* surfaces blockers and their downstream impact
|
|
175
|
+
- *"Why did we choose PostgreSQL originally?"* traces the full causal chain
|
|
176
|
+
- *"What if we switch to DynamoDB?"* maps the downstream consequences
|
|
177
|
+
|
|
178
|
+
After 20 sessions, you have a curated knowledge base of your project's decisions, not a pile of notes. Knowledge that stays relevant graduates through temporal tiers. One-off observations fade naturally.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
94
182
|
## Works With Your Stack
|
|
95
183
|
|
|
96
184
|
Drop-in adapters that implement your framework's native interface. Same API you already use — plus `query.tensions()`.
|
|
@@ -197,7 +285,7 @@ Under the hood: a local semantic graph with typed nodes, typed relationships, an
|
|
|
197
285
|
|
|
198
286
|
| Package | What | Install |
|
|
199
287
|
|:--------|:-----|:--------|
|
|
200
|
-
| [flowscript-agents](https://pypi.org/project/flowscript-agents/) | Python SDK — 9 adapters, unified memory, consolidation, audit trail | `pip install flowscript-agents` |
|
|
288
|
+
| [flowscript-agents](https://pypi.org/project/flowscript-agents/) | Python SDK — 9 adapters, unified memory, consolidation, audit trail | `pip install flowscript-agents openai` |
|
|
201
289
|
| [flowscript-core](https://www.npmjs.com/package/flowscript-core) | TypeScript SDK — Memory class, 15 tools, token budgeting, audit trail | `npm install flowscript-core` |
|
|
202
290
|
| [flowscript.org](https://flowscript.org) | Web editor, D3 visualization, live query panel | Browser |
|
|
203
291
|
|
|
@@ -22,8 +22,10 @@ mem = Memory.load_or_create("agent-memory.json",
|
|
|
22
22
|
|
|
23
23
|
## Event Types
|
|
24
24
|
|
|
25
|
-
### Python (
|
|
26
|
-
`node_create`, `
|
|
25
|
+
### Python (15 events)
|
|
26
|
+
`node_create`, `update_node`, `update_node_merge`, `node_remove`, `relationship_create`, `state_change`, `graduation`, `prune`, `session_start`, `session_end`, `session_wrap`, `consolidation`, `transcript_extract`, `consolidation_batch`, `audit_cleanup`
|
|
27
|
+
|
|
28
|
+
The `transcript_extract` event fires after each auto-extraction call with stats (nodes extracted/created/deduplicated, type breakdown, node IDs). The `consolidation_batch` event fires after consolidation with full metrics (contested/updated/related/resolved counts, collision stats, health status).
|
|
27
29
|
|
|
28
30
|
### TypeScript (14 events)
|
|
29
31
|
`node_create`, `relationship_create`, `state_change`, `modifier_add`, `session_start`, `session_end`, `session_wrap`, `graduation`, `prune`, `snapshot`, `restore`, `transcript_extract`, `budget_apply`, `audit_cleanup`
|
|
@@ -70,6 +72,15 @@ result = Memory.query_audit("agent-memory.audit.jsonl",
|
|
|
70
72
|
# → AuditQueryResult(entries=[...], total_scanned=42, files_searched=1)
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
## MCP Audit Tools
|
|
76
|
+
|
|
77
|
+
The Python MCP server exposes `query_audit` and `verify_audit` as tools (13 tools total). When configured with an `AuditConfig`, your agent can query and verify the audit trail through natural conversation:
|
|
78
|
+
|
|
79
|
+
- **`query_audit`** filters by time range, event types, node ID, session ID, adapter, and limit. Supports optional chain verification.
|
|
80
|
+
- **`verify_audit`** checks the full hash chain integrity and returns entry counts.
|
|
81
|
+
|
|
82
|
+
Both handle missing audit files gracefully (returns `valid: null` for verify, empty results for query).
|
|
83
|
+
|
|
73
84
|
## SIEM Integration
|
|
74
85
|
|
|
75
86
|
Stream audit events to Datadog, Splunk, or any monitoring system via the `on_event` callback:
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Paste the section below into your project's CLAUDE.md to enable
|
|
4
4
|
# proactive reasoning memory. FlowScript MCP tools are available
|
|
5
|
-
# when the server is configured
|
|
5
|
+
# when the server is configured in your editor's MCP settings
|
|
6
|
+
# (.claude/settings.json for Claude Code, .mcp.json for Cursor/Windsurf).
|
|
6
7
|
|
|
7
8
|
## Reasoning Memory (FlowScript)
|
|
8
9
|
|
|
@@ -28,7 +28,7 @@ import sys
|
|
|
28
28
|
from dataclasses import dataclass, field
|
|
29
29
|
from datetime import datetime, timezone
|
|
30
30
|
from pathlib import Path
|
|
31
|
-
from typing import Any, Callable, Optional
|
|
31
|
+
from typing import Any, Callable, Literal, Optional
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
# =============================================================================
|
|
@@ -50,6 +50,10 @@ class AuditConfig:
|
|
|
50
50
|
for testing or when tamper-evidence is not needed.
|
|
51
51
|
verbosity: "standard" (default) = mutation events only. "full" =
|
|
52
52
|
mutations + read/query access events (for HIPAA access auditing).
|
|
53
|
+
encryption: "none" (default) or "aes-256-gcm". Encryption at rest for
|
|
54
|
+
audit trail files. NOT YET IMPLEMENTED — v2 commitment for SOC2/
|
|
55
|
+
enterprise compliance. Setting to anything other than "none" raises
|
|
56
|
+
NotImplementedError.
|
|
53
57
|
on_event: Optional callback invoked for every audit entry. Receives the
|
|
54
58
|
full entry dict AFTER disk write. Callback failure never blocks
|
|
55
59
|
audit persistence. Use for SIEM integration, Observatory, or custom
|
|
@@ -61,8 +65,16 @@ class AuditConfig:
|
|
|
61
65
|
retention_months: Optional[int] = 84
|
|
62
66
|
hash_chain: bool = True
|
|
63
67
|
verbosity: str = "standard"
|
|
68
|
+
encryption: Literal["none", "aes-256-gcm"] = "none"
|
|
64
69
|
on_event: Optional[Callable[[dict[str, Any]], None]] = None
|
|
65
70
|
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
if self.encryption != "none":
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
f"Encryption at rest ('{self.encryption}') is not yet implemented. "
|
|
75
|
+
"This is a documented v2 commitment. Currently only 'none' is supported."
|
|
76
|
+
)
|
|
77
|
+
|
|
66
78
|
|
|
67
79
|
# =============================================================================
|
|
68
80
|
# Result Types
|
|
@@ -84,7 +96,7 @@ class AuditQueryResult:
|
|
|
84
96
|
class AuditVerifyResult:
|
|
85
97
|
"""Result of verify_audit()."""
|
|
86
98
|
|
|
87
|
-
valid: bool
|
|
99
|
+
valid: Optional[bool] # True = chain intact, False = chain broken, None = no audit trail found
|
|
88
100
|
total_entries: int
|
|
89
101
|
files_verified: int
|
|
90
102
|
legacy_entries: int = 0
|
|
@@ -654,7 +666,7 @@ class AuditWriter:
|
|
|
654
666
|
files_to_verify.append(active_path)
|
|
655
667
|
|
|
656
668
|
if not files_to_verify:
|
|
657
|
-
return AuditVerifyResult(valid=
|
|
669
|
+
return AuditVerifyResult(valid=None, total_entries=0, files_verified=0)
|
|
658
670
|
|
|
659
671
|
# Verify chain across all files
|
|
660
672
|
total_entries = 0
|
|
@@ -131,6 +131,7 @@ class FlowScriptCamelMemory(_CamelAgentMemory):
|
|
|
131
131
|
self._max_tokens = max_tokens
|
|
132
132
|
self._agent_id: str | None = None
|
|
133
133
|
self._memory.session_start()
|
|
134
|
+
self._memory.set_adapter_context("camel_ai", "FlowScriptCamelMemory", "init")
|
|
134
135
|
|
|
135
136
|
@property
|
|
136
137
|
def memory(self) -> Memory:
|
|
@@ -173,6 +174,7 @@ class FlowScriptCamelMemory(_CamelAgentMemory):
|
|
|
173
174
|
Returns:
|
|
174
175
|
List of ContextRecord objects scored for context assembly.
|
|
175
176
|
"""
|
|
177
|
+
self._memory.set_adapter_operation("retrieve")
|
|
176
178
|
records: list[ContextRecord] = []
|
|
177
179
|
|
|
178
180
|
# Order by tier priority
|
|
@@ -273,6 +275,7 @@ class FlowScriptCamelMemory(_CamelAgentMemory):
|
|
|
273
275
|
Args:
|
|
274
276
|
records: List of MemoryRecord-like objects.
|
|
275
277
|
"""
|
|
278
|
+
self._memory.set_adapter_operation("write_records")
|
|
276
279
|
# Extract content from all records
|
|
277
280
|
contents = []
|
|
278
281
|
for record in records:
|
|
@@ -399,6 +402,7 @@ class FlowScriptCamelMemory(_CamelAgentMemory):
|
|
|
399
402
|
Uses unified search (vector + keyword + temporal) when available,
|
|
400
403
|
falls back to word-level matching.
|
|
401
404
|
"""
|
|
405
|
+
self._memory.set_adapter_operation("recall")
|
|
402
406
|
if self._unified:
|
|
403
407
|
unified_results = self._unified.search(query, top_k=limit)
|
|
404
408
|
if unified_results:
|
|
@@ -452,9 +456,12 @@ class FlowScriptCamelMemory(_CamelAgentMemory):
|
|
|
452
456
|
|
|
453
457
|
def close(self):
|
|
454
458
|
"""End session: prune dormant, save. Returns SessionWrapResult."""
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
459
|
+
try:
|
|
460
|
+
if self._unified:
|
|
461
|
+
return self._unified.close()
|
|
462
|
+
return self._memory.session_wrap()
|
|
463
|
+
finally:
|
|
464
|
+
self._memory.clear_adapter_context()
|
|
458
465
|
|
|
459
466
|
def __enter__(self):
|
|
460
467
|
return self
|
|
@@ -101,6 +101,7 @@ class FlowScriptStorage:
|
|
|
101
101
|
self._rebuild_index()
|
|
102
102
|
# Start temporal session
|
|
103
103
|
self._memory.session_start()
|
|
104
|
+
self._memory.set_adapter_context("crewai", "FlowScriptStorage", "init")
|
|
104
105
|
|
|
105
106
|
@property
|
|
106
107
|
def memory(self) -> Memory:
|
|
@@ -157,6 +158,7 @@ class FlowScriptStorage:
|
|
|
157
158
|
|
|
158
159
|
def save(self, records: list[Any]) -> None:
|
|
159
160
|
"""Save MemoryRecord objects."""
|
|
161
|
+
self._memory.set_adapter_operation("save")
|
|
160
162
|
for record in records:
|
|
161
163
|
rec_id = getattr(record, "id", str(uuid.uuid4()))
|
|
162
164
|
content = getattr(record, "content", str(record))
|
|
@@ -222,6 +224,7 @@ class FlowScriptStorage:
|
|
|
222
224
|
for scoring (compares query_embedding against indexed node vectors).
|
|
223
225
|
Falls back to per-record embeddings or content matching otherwise.
|
|
224
226
|
"""
|
|
227
|
+
self._memory.set_adapter_operation("search")
|
|
225
228
|
results: list[tuple[_RecordEntry, float]] = []
|
|
226
229
|
|
|
227
230
|
# Build vector scores from VectorIndex when available
|
|
@@ -486,9 +489,12 @@ class FlowScriptStorage:
|
|
|
486
489
|
|
|
487
490
|
def close(self):
|
|
488
491
|
"""End the session: prune dormant nodes, save. Returns SessionWrapResult."""
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
+
try:
|
|
493
|
+
if self._unified:
|
|
494
|
+
return self._unified.close()
|
|
495
|
+
return self._memory.session_wrap()
|
|
496
|
+
finally:
|
|
497
|
+
self._memory.clear_adapter_context()
|
|
492
498
|
|
|
493
499
|
def __enter__(self):
|
|
494
500
|
return self
|
|
@@ -581,7 +581,7 @@ class AutoExtract:
|
|
|
581
581
|
rels_created = self._create_extraction_relationships(extraction, node_refs)
|
|
582
582
|
states_created = self._apply_extraction_states(extraction, node_refs)
|
|
583
583
|
|
|
584
|
-
|
|
584
|
+
result = IngestResult(
|
|
585
585
|
nodes_created=created,
|
|
586
586
|
nodes_deduplicated=deduped,
|
|
587
587
|
relationships_created=rels_created,
|
|
@@ -589,6 +589,14 @@ class AutoExtract:
|
|
|
589
589
|
node_ids=[ref.id for ref in node_refs],
|
|
590
590
|
)
|
|
591
591
|
|
|
592
|
+
# Audit: extraction provenance (never crash ingest for audit failures)
|
|
593
|
+
try:
|
|
594
|
+
self._write_extraction_audit(extraction, result)
|
|
595
|
+
except Exception:
|
|
596
|
+
print("AutoExtract: audit write failed (transcript_extract)", file=sys.stderr)
|
|
597
|
+
|
|
598
|
+
return result
|
|
599
|
+
|
|
592
600
|
def _ingest_with_consolidation(
|
|
593
601
|
self,
|
|
594
602
|
extraction: ExtractionResult,
|
|
@@ -636,7 +644,7 @@ class AutoExtract:
|
|
|
636
644
|
if action.target_node_id not in surviving_ids:
|
|
637
645
|
surviving_ids.append(action.target_node_id)
|
|
638
646
|
|
|
639
|
-
|
|
647
|
+
result = IngestResult(
|
|
640
648
|
nodes_created=consolidation_result.nodes_added,
|
|
641
649
|
nodes_deduplicated=consolidation_result.nodes_skipped,
|
|
642
650
|
relationships_created=rels_created + consolidation_result.nodes_related + consolidation_result.nodes_resolved,
|
|
@@ -651,6 +659,20 @@ class AutoExtract:
|
|
|
651
659
|
fallback_count=consolidation_result.fallback_count,
|
|
652
660
|
)
|
|
653
661
|
|
|
662
|
+
# Audit: extraction provenance (never crash ingest for audit failures)
|
|
663
|
+
try:
|
|
664
|
+
self._write_extraction_audit(extraction, result)
|
|
665
|
+
except Exception:
|
|
666
|
+
print("AutoExtract: audit write failed (transcript_extract)", file=sys.stderr)
|
|
667
|
+
|
|
668
|
+
# Audit: consolidation batch summary
|
|
669
|
+
try:
|
|
670
|
+
self._write_consolidation_batch_audit(consolidation_result)
|
|
671
|
+
except Exception:
|
|
672
|
+
print("AutoExtract: audit write failed (consolidation_batch)", file=sys.stderr)
|
|
673
|
+
|
|
674
|
+
return result
|
|
675
|
+
|
|
654
676
|
# -------------------------------------------------------------------------
|
|
655
677
|
# Shared helpers
|
|
656
678
|
# -------------------------------------------------------------------------
|
|
@@ -775,6 +797,50 @@ class AutoExtract:
|
|
|
775
797
|
|
|
776
798
|
return self.ingest(transcript, metadata=metadata, actor=actor)
|
|
777
799
|
|
|
800
|
+
def _write_extraction_audit(
|
|
801
|
+
self,
|
|
802
|
+
extraction: ExtractionResult,
|
|
803
|
+
result: IngestResult,
|
|
804
|
+
) -> None:
|
|
805
|
+
"""Write a transcript_extract audit event summarizing what was extracted."""
|
|
806
|
+
# Build type breakdown from extraction
|
|
807
|
+
type_counts: dict[str, int] = {}
|
|
808
|
+
for node in extraction.nodes:
|
|
809
|
+
t = node.type if node.type in _VALID_NODE_TYPES else "thought"
|
|
810
|
+
type_counts[t] = type_counts.get(t, 0) + 1
|
|
811
|
+
|
|
812
|
+
self._memory.write_audit("transcript_extract", {
|
|
813
|
+
"nodes_extracted": len(extraction.nodes),
|
|
814
|
+
"nodes_created": result.nodes_created,
|
|
815
|
+
"nodes_deduplicated": result.nodes_deduplicated,
|
|
816
|
+
"relationships_extracted": len(extraction.relationships),
|
|
817
|
+
"relationships_created": result.relationships_created,
|
|
818
|
+
"states_created": result.states_created,
|
|
819
|
+
"type_breakdown": type_counts,
|
|
820
|
+
"node_ids": result.node_ids,
|
|
821
|
+
"consolidation_used": result.consolidation_used,
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
def _write_consolidation_batch_audit(
|
|
825
|
+
self,
|
|
826
|
+
consolidation_result: Any,
|
|
827
|
+
) -> None:
|
|
828
|
+
"""Write a consolidation_batch audit event summarizing batch results."""
|
|
829
|
+
self._memory.write_audit("consolidation_batch", {
|
|
830
|
+
"nodes_added": consolidation_result.nodes_added,
|
|
831
|
+
"nodes_updated": consolidation_result.nodes_updated,
|
|
832
|
+
"nodes_related": consolidation_result.nodes_related,
|
|
833
|
+
"nodes_resolved": consolidation_result.nodes_resolved,
|
|
834
|
+
"nodes_skipped": consolidation_result.nodes_skipped,
|
|
835
|
+
"nodes_novel": consolidation_result.nodes_novel,
|
|
836
|
+
"collision_count": consolidation_result.collision_count,
|
|
837
|
+
"collisions_retried": consolidation_result.collisions_retried,
|
|
838
|
+
"error_count": consolidation_result.error_count,
|
|
839
|
+
"total_contested": consolidation_result.total_contested,
|
|
840
|
+
"llm_calls": consolidation_result.llm_calls,
|
|
841
|
+
"health_ok": consolidation_result.health_ok,
|
|
842
|
+
})
|
|
843
|
+
|
|
778
844
|
def _get_node_creator(self, type_str: str) -> Callable[[str], NodeRef]:
|
|
779
845
|
"""Get the Memory node creation method for a type string.
|
|
780
846
|
|
|
@@ -90,6 +90,7 @@ class FlowScriptMemoryService(_ADKBaseMemoryService):
|
|
|
90
90
|
self._file_path = file_path
|
|
91
91
|
# Start temporal session
|
|
92
92
|
self._memory.session_start()
|
|
93
|
+
self._memory.set_adapter_context("google_adk", "FlowScriptMemoryService", "init")
|
|
93
94
|
|
|
94
95
|
@property
|
|
95
96
|
def memory(self) -> Memory:
|
|
@@ -128,6 +129,7 @@ class FlowScriptMemoryService(_ADKBaseMemoryService):
|
|
|
128
129
|
to create typed reasoning nodes from session content. Otherwise,
|
|
129
130
|
stores raw content as thought nodes.
|
|
130
131
|
"""
|
|
132
|
+
self._memory.set_adapter_operation("add_session")
|
|
131
133
|
app_name = getattr(session, "app_name", "unknown")
|
|
132
134
|
user_id = getattr(session, "user_id", "unknown")
|
|
133
135
|
session_id = getattr(session, "id", "unknown")
|
|
@@ -206,6 +208,7 @@ class FlowScriptMemoryService(_ADKBaseMemoryService):
|
|
|
206
208
|
|
|
207
209
|
Returns ADK SearchMemoryResponse with MemoryEntry objects.
|
|
208
210
|
"""
|
|
211
|
+
self._memory.set_adapter_operation("search_memory")
|
|
209
212
|
# Use unified search when available (vector + keyword + temporal)
|
|
210
213
|
if self._unified:
|
|
211
214
|
unified_results = self._unified.search(query, top_k=10)
|
|
@@ -321,6 +324,7 @@ class FlowScriptMemoryService(_ADKBaseMemoryService):
|
|
|
321
324
|
custom_metadata: Mapping[str, object] | None = None,
|
|
322
325
|
) -> None:
|
|
323
326
|
"""Incremental event addition."""
|
|
327
|
+
self._memory.set_adapter_operation("add_events")
|
|
324
328
|
prev_ref = None
|
|
325
329
|
for event in events:
|
|
326
330
|
content = _extract_event_content(event)
|
|
@@ -356,9 +360,12 @@ class FlowScriptMemoryService(_ADKBaseMemoryService):
|
|
|
356
360
|
|
|
357
361
|
def close(self):
|
|
358
362
|
"""End the session: prune dormant nodes, save. Returns SessionWrapResult."""
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
363
|
+
try:
|
|
364
|
+
if self._unified:
|
|
365
|
+
return self._unified.close()
|
|
366
|
+
return self._memory.session_wrap()
|
|
367
|
+
finally:
|
|
368
|
+
self._memory.clear_adapter_context()
|
|
362
369
|
|
|
363
370
|
def __enter__(self):
|
|
364
371
|
return self
|