feat(flows): per-flow memory namespace + post-run digest (flow:<id>) - #5176
Conversation
Gives a running flow a private, sandboxed memory namespace so it can remember what it already did across runs — e.g. a scheduled newsletter-digest flow tracking which items it already sent, to avoid re-sending them on the next scheduled run, without re-deriving that state from the target service each time. Adds: - `flow_namespace(flow_id)` (`src/openhuman/flows/mod.rs`) — the single function that builds a flow's namespace string. - `flow_memory_recall` / `flow_memory_remember` agent tools (`src/openhuman/flows/memory_tools.rs`), registered under the `flows` tool-registry gate and the `FLOWS` domain-group name list. - `FlowRunDigestSubscriber` (`src/openhuman/flows/bus.rs`) — on a successful `FlowRunFinished`, writes a compact, bounded summary of the run into the flow's namespace, with a best-effort retention cap. Registered in `src/core/jsonrpc.rs` alongside `FlowTriggerSubscriber`. - A `flows_delete` cleanup hook that clears a deleted flow's namespace. Security invariant (non-negotiable, preserved throughout): there is no code path by which a flow can write to, or even name, a namespace other than its own. `flow_memory_remember` derives the namespace internally from `flow_id` via `flow_namespace` — it has no `namespace` parameter a caller could override — and every write is tainted `MemoryTaint::ExternalSync` (automation output, never treated as a user-authored fact). `flow_memory_recall`'s `scope: "flows"` is read-only cross-flow visibility, confined to `flow_*` namespaces, and can never see or write to the user's personal/global memory. Reuses the existing autonomy-tier + approval gate (`SecurityPolicy:: enforce_tool_operation`) for writes; no new permission path. Deviates from the originally specced `flow:<id>` (colon) separator: `UnifiedMemory`'s namespace sanitizer collapses `:` to `_` on the store/recall/list/clear_namespace paths but NOT on `forget`, which would silently break the digest subscriber's retention sweep and `scope: "flows"` cross-flow listing. `flow_<id>` (underscore) is already a fixed point of that sanitizer, so every `Memory` method agrees on the same literal namespace string. Documented in `flow_namespace`'s doc comment. Closes tinyhumansai#5173
📝 WalkthroughWalkthroughAdds per-flow semantic memory tools, bounded digests for successful flow runs, retention pruning, namespace cleanup on flow deletion, and Flows-domain registration with tests. ChangesFlow memory lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant FlowRun as Flow run
participant EventBus
participant Digest as FlowRunDigestSubscriber
participant Memory as Memory store
FlowRun->>EventBus: publish FlowRunFinished
EventBus->>Digest: dispatch event
Digest->>Memory: store bounded successful-run digest
Digest->>Memory: prune excess digest entries
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
Formatting-only (rustfmt --edition 2021) on the files this PR adds/edits. The remaining Rust Quality diffs (caps.rs, provider.rs) are pre-existing on upstream/main — head e780277 already fails Rust Quality from tinyhumansai#5151 et al. — and are untouched here.
…ts are isolated The FlowRunDigestSubscriber tests bound the process-global memory client via memory::global::init(tempdir), then read the digest back through memory::global::client(). But the global is a one-shot OnceLock: when an earlier test in the same binary has already initialised it, init(tempdir) silently no-ops (memory::global's own tests document this), so the subscriber wrote to — and the test read from — a contaminated/dropped workspace. digest_writes_run_digest_entry_for_completed_run then found no entry and panicked. Add a test-only memory-injection seam (memory_override + with_memory + resolve_memory); production is unchanged (None -> active_memory_client, the process-global). The four digest tests now inject a directly-constructed UnifiedMemory(NoopEmbedding) — the same shape flows::memory_tools' tests use — and read back through that same instance, so write/read are deterministic and isolated from the global singleton.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bb7d10787
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| let flow_id = args | ||
| .get("flow_id") | ||
| .and_then(|v| v.as_str()) | ||
| .ok_or_else(|| anyhow::anyhow!("Missing 'flow_id' parameter"))?; |
There was a problem hiding this comment.
Bind flow memory writes to the active flow
Because flow_id is accepted as an ordinary model-supplied tool argument, any caller that can invoke flow_memory_remember (including a native oh:flow_memory_remember tool-call node or another agent context) can pass another saved flow's id and write into flow_<that id>; the write path then treats the entry as if that flow produced it, so later flow_memory_recall or digest-based dedupe can consume poisoned cross-flow state. The namespace should be derived from trusted run/tool context, or at least verified against the active flow, before storing.
Useful? React with 👍 / 👎.
| pub fn flow_namespace(flow_id: &str) -> String { | ||
| format!("{FLOW_MEMORY_NAMESPACE_PREFIX}{flow_id}") |
There was a problem hiding this comment.
Move namespace construction out of mod.rs
This adds the flow-memory namespace constant/helper and tests directly to mod.rs, but the repository's canonical module shape says mod.rs should stay export-focused only and domain logic belongs in sibling modules such as types.rs, ops.rs, or tools.rs. Keeping this security-sensitive namespace construction in mod.rs makes the flows domain harder to follow and violates that documented layout; move it to a sibling module and re-export it from here.
AGENTS.md reference: AGENTS.md:L191-L202
Useful? React with 👍 / 👎.
|
| Filename | Overview |
|---|---|
| src/openhuman/flows/memory_tools.rs | New file: implements FlowMemoryRecallTool and FlowMemoryRememberTool with flow_namespace helper; trusted-origin guard for flow_id is well-designed but scope: flows issues O(N) sequential recall queries and the flow_id arg path outside a trusted workflow context allows arbitrary namespace targeting (previously flagged). |
| src/openhuman/flows/bus.rs | Adds FlowRunDigestSubscriber: correct status filtering, dependency-injected memory for tests, and retention cap logic — but step output is stored in digests without the has_likely_secret guard that flow_memory_remember applies. |
| src/openhuman/flows/ops.rs | Splits flows_delete into flows_delete + flows_delete_impl to allow memory-override injection; best-effort namespace clear is correctly placed after the store row removal and never fails the delete. |
| src/openhuman/tools/ops.rs | Registers FlowMemoryRecallTool and FlowMemoryRememberTool under the flows feature gate; adds both names to the FLOWS domain-group constant with the correct updated count comment. |
| src/core/jsonrpc.rs | Registers FlowRunDigestSubscriber inside the existing group_first_time(DomainGroup::Flows) block, consistent with FlowTriggerSubscriber registration; std::mem::forget pattern matches existing subscriber registration. |
| src/openhuman/flows/ops_tests.rs | Adds flows_delete_clears_flow_memory_namespace test using injected MemoryClient to avoid the global singleton race; the memory_client_override injection seam is sound and flows_delete_impl being non-pub is correctly accessible from the child test module. |
| src/openhuman/tools/ops_tests.rs | Adds the two new tool names to the FLOWS domain-group assertion lists; straightforward test update. |
| src/openhuman/flows/mod.rs | Adds memory_tools submodule declaration and re-exports flow_namespace / FLOW_MEMORY_NAMESPACE_PREFIX; clean additive change. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Flow Run Executes] --> B{Run Status?}
B -->|completed / completed_with_warnings| C[FlowRunDigestSubscriber]
B -->|failed / cancelled| Z[Ignored — no digest written]
C --> D[store::get_flow → flow_name]
C --> E[store::get_flow_run → steps]
D & E --> F[render_run_digest bounded to 1000 chars]
F --> G[memory.store_with_taint namespace: flow_id taint: ExternalSync]
G --> H[enforce_retention_cap keep newest 50 run_digest entries]
I[Agent inside flow run] -->|trusted_flow_id| J{scope?}
K[Agent outside flow run] -->|flow_id arg| J
J -->|scope: flow| L[memory.recall own namespace only]
J -->|scope: flows| M[recall per flow namespace]
M --> N[merge + sort + truncate to limit]
O[flow_memory_remember] --> P{trusted origin?}
P -->|yes| Q[namespace from trusted_id]
P -->|no| R[namespace from arg_flow_id]
Q & R --> S[has_likely_secret check]
S -->|clean| T[memory.store_with_taint]
S -->|secret-like| U[Reject]
V[flows_delete] --> W[store::remove_flow]
W --> X[memory_client.clear_namespace best-effort]
Reviews (2): Last reviewed commit: "fix(flows): address review — bind flow m..." | Re-trigger Greptile
…isolate delete test, relocate namespace helper
- Codex P1 (SECURITY, cross-flow write): FlowMemoryRememberTool and
FlowMemoryRecallTool's scope:"flow" case now resolve the governing flow id
from the run's AgentTurnOrigin::TrustedAutomation { job_id, source:
TrustedAutomationSource::Workflow { .. } } task-local when present, ignoring
any model-supplied flow_id arg outright. A prompt-injected caller can no
longer name a different flow's id to poison its namespace. Falls back to
the flow_id arg (still required) only when invoked outside a flow run.
Added a unit test scoping a mismatched flow_id arg inside a Workflow origin
and asserting the write lands only in the trusted flow's namespace.
- Greptile P1 (test isolation): flows_delete_clears_flow_memory_namespace
relied on the memory::global OnceLock singleton, racy across parallel
tests in the same binary. flows_delete now delegates to a private
flows_delete_impl(config, id, memory_client_override) — mirroring
FlowRunDigestSubscriber::with_memory — and the test injects a
directly-constructed MemoryClient instead of the global, seeding and
reading back through the same instance the delete call clears.
- Codex P2 (module shape): moved FLOW_MEMORY_NAMESPACE_PREFIX, flow_namespace,
and their unit tests out of flows::mod.rs into memory_tools.rs (the domain
sibling that owns the tools consuming them), re-exporting from mod.rs so
every existing flows::flow_namespace / flows::FLOW_MEMORY_NAMESPACE_PREFIX
call site keeps resolving unchanged.
- Greptile P2 (clarity): commented why FlowRunDigestSubscriber::domains()
returns ["cron"] — FlowRunFinished is tagged "cron" by DomainEvent::domain().
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/openhuman/flows/memory_tools.rs (3)
258-295: 🚀 Performance & Scalability | 🔵 Trivial
scope: "flows"recall scans every flow namespace per call.Each cross-flow recall iterates all namespaces matching
flow_and issues a separate semanticrecallper namespace, with no cap on namespace count. This scales linearly with the number of flows a user has created over time, which could become a slow hot path if flow count grows large.Consider capping the number of namespaces scanned (or short-circuiting once enough high-scoring results are found) if flow counts are expected to grow unbounded.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/flows/memory_tools.rs` around lines 258 - 295, Cap the number of flow namespaces scanned in the "flows" branch of the memory tool before issuing per-namespace recall calls. Apply the cap to the filtered namespace iteration while preserving score sorting, truncation to limit, and existing per-namespace error handling.
1-785: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFile exceeds the ~500-line guideline; tests weren't run for this gated-surface change.
This file is ~785 lines. As per coding guidelines,
**/*.{rs,ts,tsx}files should be "approximately 500 lines or fewer" — the same guidelines explicitly allow moving tests to a sibling*_tests.rsfile, which would bring this file back under the target.Separately, the PR notes Rust validation commands weren't run. As per coding guidelines for
src/**/*.rs, gated-domain changes should "run disabled-build tests after changing gated surfaces because CI smoke checks do not compile test code" — worth doing before merge sinceflowsis a feature gate.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/flows/memory_tools.rs` around lines 1 - 785, Move the tests from the oversized FlowMemoryRecallTool and FlowMemoryRememberTool module into a sibling memory_tools_tests.rs test module, keeping production implementations and shared helpers in memory_tools.rs so it is approximately 500 lines or fewer. Then run the required disabled-build Rust tests for the gated flows surface and report or fix any failures before merging.Source: Coding guidelines
690-731: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a symmetric trusted-origin test for
flow_memory_recall.
remember_ignores_mismatched_flow_id_arg_inside_trusted_workflow_runcovers the security fix for the write path, but there's no equivalent test assertingflow_memory_recallalso ignores a mismatched, model-suppliedflow_idwhen a trustedWorkfloworigin is set (i.e. that scope="flow" reads from the trusted flow's own namespace, not the arg's).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/flows/memory_tools.rs` around lines 690 - 731, The trusted-origin security coverage only tests the remember write path. Add a symmetric async test for FlowMemoryRecallTool, modeled on remember_ignores_mismatched_flow_id_arg_inside_trusted_workflow_run, that seeds data in the trusted workflow flow_namespace, executes recall with a different flow_id and scope="flow" under AgentTurnOrigin::TrustedAutomation { source: Workflow }, and asserts the trusted value is returned rather than reading the mismatched namespace.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/openhuman/flows/memory_tools.rs`:
- Around line 209-237: Extract the duplicated trusted-origin versus
model-supplied flow ID selection from both memory-tool execute methods into a
shared resolve_flow_id helper. Have it accept the optional argument and tool
name, preserve trusted_flow_id precedence and diagnostic logging, and return the
resolved String or missing-parameter error; keep each caller’s existing
post-resolution empty-string validation and error style.
---
Nitpick comments:
In `@src/openhuman/flows/memory_tools.rs`:
- Around line 258-295: Cap the number of flow namespaces scanned in the "flows"
branch of the memory tool before issuing per-namespace recall calls. Apply the
cap to the filtered namespace iteration while preserving score sorting,
truncation to limit, and existing per-namespace error handling.
- Around line 1-785: Move the tests from the oversized FlowMemoryRecallTool and
FlowMemoryRememberTool module into a sibling memory_tools_tests.rs test module,
keeping production implementations and shared helpers in memory_tools.rs so it
is approximately 500 lines or fewer. Then run the required disabled-build Rust
tests for the gated flows surface and report or fix any failures before merging.
- Around line 690-731: The trusted-origin security coverage only tests the
remember write path. Add a symmetric async test for FlowMemoryRecallTool,
modeled on remember_ignores_mismatched_flow_id_arg_inside_trusted_workflow_run,
that seeds data in the trusted workflow flow_namespace, executes recall with a
different flow_id and scope="flow" under AgentTurnOrigin::TrustedAutomation {
source: Workflow }, and asserts the trusted value is returned rather than
reading the mismatched namespace.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 38935979-3e75-4269-8158-1fd45e356aa9
📒 Files selected for processing (5)
src/openhuman/flows/bus.rssrc/openhuman/flows/memory_tools.rssrc/openhuman/flows/mod.rssrc/openhuman/flows/ops.rssrc/openhuman/flows/ops_tests.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- src/openhuman/flows/ops_tests.rs
- src/openhuman/flows/ops.rs
- src/openhuman/flows/bus.rs
|
|
||
| let flow_id_arg = args.get("flow_id").and_then(|v| v.as_str()).map(str::trim); | ||
|
|
||
| // SECURITY: inside a running flow, the run's own trusted origin is | ||
| // the ONLY authoritative source for "which flow is calling" — never | ||
| // the model-supplied `flow_id` arg. Without this, a prompt-injected | ||
| // caller could pass a different flow's id and read across the | ||
| // sandbox boundary the module doc promises. See `trusted_flow_id`. | ||
| let trusted = trusted_flow_id(); | ||
| let flow_id: String = match &trusted { | ||
| Some(trusted_id) => { | ||
| tracing::debug!( | ||
| target: "flows", | ||
| flow_id = %trusted_id, | ||
| "[flows:memory] flow_memory_recall: flow id resolved from the trusted Workflow \ | ||
| run origin (any model-supplied flow_id arg is ignored)" | ||
| ); | ||
| trusted_id.clone() | ||
| } | ||
| None => { | ||
| let arg = | ||
| flow_id_arg.ok_or_else(|| anyhow::anyhow!("Missing 'flow_id' parameter"))?; | ||
| if arg.is_empty() { | ||
| return Err(anyhow::anyhow!("flow_id cannot be empty")); | ||
| } | ||
| arg.to_string() | ||
| } | ||
| }; | ||
| let flow_id = flow_id.as_str(); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Duplicated, security-critical flow_id resolution logic.
The trusted-origin-vs-arg resolution block is duplicated almost verbatim in both tools, with subtly different empty-string handling (recall returns a hard Err; remember returns a soft ToolResult::error). Since this is the exact mechanism preventing a forged flow_id from redirecting reads/writes across the sandbox boundary, having it live in two places risks future edits patching only one copy.
Extract a shared helper (e.g. resolve_flow_id) that both execute() methods call, letting each keep its own post-resolution validation/error style:
♻️ Suggested extraction
fn resolve_flow_id(flow_id_arg: Option<&str>, tool: &str) -> anyhow::Result<String> {
match trusted_flow_id() {
Some(trusted_id) => {
tracing::debug!(
target: "flows",
flow_id = %trusted_id,
tool,
"[flows:memory] flow id resolved from trusted Workflow run origin (model-supplied flow_id arg ignored)"
);
Ok(trusted_id)
}
None => flow_id_arg
.map(str::to_string)
.ok_or_else(|| anyhow::anyhow!("Missing 'flow_id' parameter")),
}
}Based on learnings, security-critical resolution logic like this benefits from a single source of truth to avoid drift between call sites.
Also applies to: 397-424
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/flows/memory_tools.rs` around lines 209 - 237, Extract the
duplicated trusted-origin versus model-supplied flow ID selection from both
memory-tool execute methods into a shared resolve_flow_id helper. Have it accept
the optional argument and tool name, preserve trusted_flow_id precedence and
diagnostic logging, and return the resolved String or missing-parameter error;
keep each caller’s existing post-resolution empty-string validation and error
style.
… through the engine
Proves the memory node through the FULL stack: tinyflows compile/run
dispatching through the real OpenHumanMemory host adapter (build_capabilities),
against a real on-disk Memory store — not the crate's MockMemory, and not the
adapter tested in isolation.
- flow-scope remember -> recall round-trip across two separate compiled
graphs/engine runs under the same TrustedAutomation{Workflow} flow_id
- coherence: the memory-node write is visible via the sibling
flow_memory_recall agent tool (tinyhumansai#5176) against the same store
- security invariant end-to-end: a remember/scope:"user" node is rejected at
tinyflows' own validate/compile step, the adapter independently hard-refuses
it too, and the user's real GLOBAL_NAMESPACE store stays untouched
- dry_run_workflow's MockMemory path still works with a memory node and never
touches the real store
No bugs found in the adapter/engine wiring itself.
Summary
flow_<flow_id>) so it can remember what it already did across runs — e.g. a scheduled newsletter-digest flow tracking which items it already sent, avoiding re-sends without re-deriving state from the target service.flow_memory_recall(read-only, own-namespace or cross-flowscope: "flows") andflow_memory_remember(write, own-namespace only), registered under the existingflowscompile-time gate.FlowRunDigestSubscriber— on a successfulFlowRunFinishedevent, writes a compact, bounded (~1000 char) summary of the run into the flow's own namespace, with a best-effort retention cap of 50 digests.flows_deletenow clears the deleted flow's memory namespace (best-effort).Problem
A flow (tinyflows graph) has no durable, flow-scoped place to remember facts about its own past runs. A scheduled digest/notification flow can't tell what it already sent last time without either re-deriving that from the target service (lossy/rate-limited) or writing into the user's own personal memory (a security/data-hygiene problem — automation output must never masquerade as user-authored fact).
Solution
flow_namespace(flow_id)(src/openhuman/flows/mod.rs) is the single function that builds a flow's namespace string. Security invariant: it is the only place in the codebase that constructs this namespace — every caller (the two new tools, the digest subscriber, the delete hook) passes aflow_id, never a raw namespace, so a flow can never write to, or even name, any namespace but its own.flow_memory_rememberhas nonamespaceparameter at all — the namespace is derived internally — and every write is taintedMemoryTaint::ExternalSync(neverInternal/user-authored), matching how external-sync ingestion pipelines taint third-party content, so the subconscious gate treats flow-written memory exactly as conservatively.flow_memory_recall'sscope: "flows"is read-only cross-flow visibility confined toflow_*namespaces (e.g. so a family of related flows can dedupe against each other) — it can never see or write to the user's personal/global memory.SecurityPolicy::enforce_tool_operation(ToolOperation::Act, ...)) for the write tool — no new permission path invented.flow:<id>(colon) separator: while implementing, I foundUnifiedMemory's namespace sanitizer (memory_store/namespace_store/init.rs) collapses any character outside[A-Za-z0-9_/-]— including:— to_on thestore_with_taint/recall/list/MemoryClient::clear_namespacepaths, butMemory::forget(memory_store/memory_trait.rs) does not sanitize itsnamespaceargument before querying. With aflow:<id>namespace,forgetwould silently never match the rowstore_with_taintactually persisted — breaking the digest subscriber's retention sweep — andnamespace_summaries()-based cross-flow listing would need to match the sanitized (not literal) form anyway, sincenamespace_summariesreads the persisted column back verbatim. Usingflow_<id>(underscore) is already a fixed point of the sanitizer (flow ids are hyphenated UUIDs), so everyMemorymethod agrees on the exact same namespace string with no silent mismatch. Fully documented inflow_namespace's doc comment.Submission Checklist
src/openhuman/flows/memory_tools.rs(tool unit tests: empty recall, store→recall round-trip, scope isolation/crossing, missing-param errors, taint assertion, ReadOnly-autonomy block, secret-content guard, own-namespace-only write),src/openhuman/flows/bus.rs(digest subscriber: name/domains, ignores unrelated events, ignores failed/cancelled runs, writes digest for completed/completed_with_warnings, digest rendering bounds),src/openhuman/flows/ops_tests.rs(flows_deleteclears the namespace),src/openhuman/flows/mod.rs(flow_namespaceunit tests),src/openhuman/tools/ops_tests.rs(domain-group classification for the two new tool names).pnpm test:coverage/pnpm test:rust— worked under an explicit constraint not to runcargo/build tooling in this session; coverage numbers are unverified. CI will report the real diff-coverage number.flowsRust domain, covered by inline unit tests in the same style as its sibling subscribers/tools, not a new top-level user-facing feature area.Memory/MemoryClientstorage layer.Closes #5173below.Impact
openhuman-core), gated behind the existingflowscompile-time feature (default ON, forwarded to the desktop shell like the rest of theflowsdomain). No frontend/UI change.SecurityPolicy::enforce_tool_operation.FlowRunFinishedfires).flow_*) that did not exist before. No existing namespace or table is touched other thanflow_runs/flow_definitionsreads (unchanged) andmemory_docswrites scoped to the new namespace family.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
feat/flows-memory-namespaceValidation Run
pnpm --filter openhuman-app format:check— not run (no frontend changes; session constraint against running build tooling)pnpm typecheck— not run (no frontend changes)cargoinvocations. Validated entirely by re-reading live source for every import path, signature, and event-variant shape used.cargo check,cargo fmt --check,rust:check) is the first real compiler pass on this diff.app/src-taurichanges.Validation Blocked
command:cargo check/cargo testerror:N/A — not attempted; blocked by an explicit session constraint ("DO NOT run cargo"), not a tooling failure.impact:Diff has not been compiler-verified. Every symbol, import path, function signature, and struct/enum shape used was individually confirmed by reading the live source (tinycortexMemorytrait,UnifiedMemorysanitizer/forgetbehavior,flows::store/typessignatures,DomainEvent::FlowRunFinishedshape,SecurityPolicy/ToolOperation, existing sibling tool/subscriber patterns) rather than by compiling. CI's Rust lanes are the first actual build of this code.Behavior Changes
flow_memory_recall/flow_memory_remember, and a flow's memory persists lightweight run summaries automatically.Parity Contract
flow_memory_recall/flow_memory_rememberwere added to theFLOWSdomain-group name list insrc/openhuman/tools/ops.rs(and its mirrored test lists) so they classify correctly under the runtimeDomainSetgate rather than silently falling through toPlatform.Duplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Bug Fixes