Skip to content

feat(flows): per-flow memory namespace + post-run digest (flow:<id>) - #5176

Merged
graycyrus merged 6 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-memory-namespace
Jul 24, 2026
Merged

feat(flows): per-flow memory namespace + post-run digest (flow:<id>)#5176
graycyrus merged 6 commits into
tinyhumansai:mainfrom
graycyrus:feat/flows-memory-namespace

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Gives a running flow a private, sandboxed memory namespace (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.
  • Adds two new agent tools, flow_memory_recall (read-only, own-namespace or cross-flow scope: "flows") and flow_memory_remember (write, own-namespace only), registered under the existing flows compile-time gate.
  • Adds FlowRunDigestSubscriber — on a successful FlowRunFinished event, 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_delete now 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 a flow_id, never a raw namespace, so a flow can never write to, or even name, any namespace but its own.
  • flow_memory_remember has no namespace parameter at all — the namespace is derived internally — and every write is tainted MemoryTaint::ExternalSync (never Internal/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's scope: "flows" is read-only cross-flow visibility confined to flow_* 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.
  • Reuses the existing autonomy-tier + approval gate (SecurityPolicy::enforce_tool_operation(ToolOperation::Act, ...)) for the write tool — no new permission path invented.
  • Deliberate deviation from the originally specced flow:<id> (colon) separator: while implementing, I found UnifiedMemory's namespace sanitizer (memory_store/namespace_store/init.rs) collapses any character outside [A-Za-z0-9_/-] — including : — to _ on the store_with_taint/recall/list/MemoryClient::clear_namespace paths, but Memory::forget (memory_store/memory_trait.rs) does not sanitize its namespace argument before querying. With a flow:<id> namespace, forget would silently never match the row store_with_taint actually persisted — breaking the digest subscriber's retention sweep — and namespace_summaries()-based cross-flow listing would need to match the sanitized (not literal) form anyway, since namespace_summaries reads the persisted column back verbatim. Using flow_<id> (underscore) is already a fixed point of the sanitizer (flow ids are hyphenated UUIDs), so every Memory method agrees on the exact same namespace string with no silent mismatch. Fully documented in flow_namespace's doc comment.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — see 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_delete clears the namespace), src/openhuman/flows/mod.rs (flow_namespace unit tests), src/openhuman/tools/ops_tests.rs (domain-group classification for the two new tool names).
  • N/A: could not run pnpm test:coverage / pnpm test:rust — worked under an explicit constraint not to run cargo/build tooling in this session; coverage numbers are unverified. CI will report the real diff-coverage number.
  • N/A: no dedicated Flows/automations row exists in the coverage matrix to extend — this adds a sub-capability to the existing flows Rust domain, covered by inline unit tests in the same style as its sibling subscribers/tools, not a new top-level user-facing feature area.
  • N/A: no matrix feature IDs apply (see above).
  • No new external network dependencies introduced — pure in-process Rust, reuses the existing Memory/MemoryClient storage layer.
  • N/A: does not touch release-cut/manual-smoke surfaces (no UI change; backend-only agent tools + event subscriber).
  • Linked issue closed via Closes #5173 below.

Impact

  • Runtime/platform: Rust core only (openhuman-core), gated behind the existing flows compile-time feature (default ON, forwarded to the desktop shell like the rest of the flows domain). No frontend/UI change.
  • Security: new write surface is intentionally narrow and sandboxed — see Solution section. No new permission model; reuses SecurityPolicy::enforce_tool_operation.
  • Performance: digest write + retention sweep run on the existing best-effort event-bus subscriber path; failures are logged and swallowed, never block or fail the flow run itself (the run has already settled by the time FlowRunFinished fires).
  • Migration/compatibility: additive only — new tools, new subscriber, new namespace family (flow_*) that did not exist before. No existing namespace or table is touched other than flow_runs/flow_definitions reads (unchanged) and memory_docs writes scoped to the new namespace family.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/flows-memory-namespace
  • Commit SHA: 1205b86

Validation Run

  • N/A: pnpm --filter openhuman-app format:check — not run (no frontend changes; session constraint against running build tooling)
  • N/A: pnpm typecheck — not run (no frontend changes)
  • N/A: Focused tests not run — session constraint: no cargo invocations. Validated entirely by re-reading live source for every import path, signature, and event-variant shape used.
  • N/A: Rust fmt/check not run — same constraint. CI (cargo check, cargo fmt --check, rust:check) is the first real compiler pass on this diff.
  • N/A: Tauri fmt/check — no app/src-tauri changes.

Validation Blocked

  • command: cargo check / cargo test
  • error: 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 (tinycortex Memory trait, UnifiedMemory sanitizer/forget behavior, flows::store/types signatures, DomainEvent::FlowRunFinished shape, 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

  • Intended behavior change: flows gain a new, private memory namespace and two new opt-in agent tools; successful flow runs additionally get a best-effort memory digest written after they finish; deleting a flow additionally clears its memory namespace. No existing RPC, tool, or event behavior is modified — this is additive.
  • User-visible effect: none directly (no UI surfaces this yet); an agent authoring/running a flow gains the ability to call flow_memory_recall/flow_memory_remember, and a flow's memory persists lightweight run summaries automatically.

Parity Contract

  • Legacy behavior preserved: yes — no existing controller, tool, or subscriber registration was changed; only new entries were added alongside them under the same feature gate.
  • Guard/fallback/dispatch parity checks: flow_memory_recall/flow_memory_remember were added to the FLOWS domain-group name list in src/openhuman/tools/ops.rs (and its mirrored test lists) so they classify correctly under the runtime DomainSet gate rather than silently falling through to Platform.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none known
  • Canonical PR: this one
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Added flow-scoped tools to recall and securely remember information within sandboxed flow memory.
    • Flow runs now produce compact run digests stored in flow private memory on successful completion (including “completed with warnings”).
    • Run digests are subject to a per-flow retention limit.
    • When flow capabilities are enabled, the new memory tools are available; deleting a flow also clears its associated flow memory namespace.
  • Bug Fixes

    • Prevented digest creation for failed or cancelled runs.
    • Improved safety around flow memory isolation, authorization, and secret-like content handling.

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
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds per-flow semantic memory tools, bounded digests for successful flow runs, retention pruning, namespace cleanup on flow deletion, and Flows-domain registration with tests.

Changes

Flow memory lifecycle

Layer / File(s) Summary
Flow memory namespace and tools
src/openhuman/flows/memory_tools.rs, src/openhuman/flows/mod.rs, src/openhuman/tools/...
Defines derived namespaces, adds isolated recall and authorized remember tools, registers them under the flows feature, and tests schemas, tainting, isolation, and gating.
Successful run digest persistence
src/openhuman/flows/bus.rs, src/core/jsonrpc.rs
Stores bounded digests for successful flow-run completions, prunes old entries, and validates event handling and formatting.
Flow deletion cleanup
src/openhuman/flows/ops.rs, src/openhuman/flows/ops_tests.rs
Clears the derived memory namespace after deletion and verifies stored entries are removed.

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
Loading

Possibly related PRs

Suggested labels: feature, rust-core

Suggested reviewers: m3ga-mind, senamakel

Poem

I’m a bunny with memories tucked out of sight,
Each flow keeps its own little burrow just right.
Runs leave a digest, concise and bright,
Old notes hop away when the cap says goodnight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Core memory and digest features land, but the requested JSON-RPC remember→recall e2e is missing and the namespace shape differs from the linked issue. Add the JSON-RPC round-trip test and, if the issue remains authoritative, switch the flow namespace to the requested flow: form.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main flow-scoped memory namespace and post-run digest changes.
Out of Scope Changes check ✅ Passed The diff stays focused on flow memory, digest, deletion cleanup, and tool registry wiring.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Comment @coderabbitai help to get the list of available commands.

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.
@graycyrus
graycyrus marked this pull request as ready for review July 24, 2026 10:36
@graycyrus
graycyrus requested a review from a team July 24, 2026 10:36
@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 24, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/openhuman/flows/memory_tools.rs Outdated
Comment on lines +269 to +272
let flow_id = args
.get("flow_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'flow_id' parameter"))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread src/openhuman/flows/mod.rs Outdated
Comment on lines +88 to +89
pub fn flow_namespace(flow_id: &str) -> String {
format!("{FLOW_MEMORY_NAMESPACE_PREFIX}{flow_id}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 24, 2026
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds per-flow sandboxed memory to the flows system: two new agent tools (flow_memory_recall / flow_memory_remember) that scope reads/writes to a flow's own flow_<id> namespace, a FlowRunDigestSubscriber that automatically records a compact run summary on successful completion, and a flows_delete cleanup hook that clears the namespace. The trusted-origin guard (trusted_flow_id()) correctly ignores the model-supplied flow_id argument when executing inside a real flow run, which is the right design for the write case.

  • Memory namespace design: flow_namespace correctly uses flow_<id> (underscore) rather than flow:<id> (colon) to stay a fixed point of UnifiedMemory's sanitizer, preventing the silent mismatch the PR description details.
  • Digest subscriber: Correctly filters to completed/completed_with_warnings, dependency-injects a Memory instance for test isolation, and enforces a 50-entry retention cap scoped only to run_digest:* keys \u2014 but omits the has_likely_secret guard that flow_memory_remember applies to explicit writes.
  • scope: \"flows\" cross-namespace recall: Makes one sequential memory.recall call per flow namespace before merging, which becomes O(N) queries for workspaces with many flows.

Confidence Score: 4/5

Safe to merge with awareness of the flow_id fallback path and the missing secret guard in auto-generated digests.

The core architecture is sound — the trusted-origin guard, taint labelling, and namespace derivation are all correct. The flow_id fallback path (noted in the previous review) still accepts a model-supplied value when no trusted workflow origin is present, and the digest subscriber stores step output without the has_likely_secret check that the explicit remember tool applies, meaning credential-like strings in node output could persist to the memory store.

src/openhuman/flows/memory_tools.rs (trusted-origin fallback path) and src/openhuman/flows/bus.rs (render_run_digest step output handling).

Important Files Changed

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]
Loading

Reviews (2): Last reviewed commit: "fix(flows): address review — bind flow m..." | Re-trigger Greptile

Comment thread src/openhuman/flows/ops_tests.rs
Comment thread src/openhuman/flows/bus.rs
…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().

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 semantic recall per 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 win

File 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.rs file, 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 since flows is 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 win

Add a symmetric trusted-origin test for flow_memory_recall.

remember_ignores_mismatched_flow_id_arg_inside_trusted_workflow_run covers the security fix for the write path, but there's no equivalent test asserting flow_memory_recall also ignores a mismatched, model-supplied flow_id when a trusted Workflow origin 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb7d10 and c5d36c6.

📒 Files selected for processing (5)
  • src/openhuman/flows/bus.rs
  • src/openhuman/flows/memory_tools.rs
  • src/openhuman/flows/mod.rs
  • src/openhuman/flows/ops.rs
  • src/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

Comment on lines +209 to +237

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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@graycyrus
graycyrus merged commit b9e0ce6 into tinyhumansai:main Jul 24, 2026
25 checks passed
graycyrus added a commit to graycyrus/openhuman that referenced this pull request Jul 28, 2026
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(flows): per-flow memory namespace + post-run digest (flow:<id>)

1 participant