Skip to content

feat(learning): explicit user-preference tool + always-on pinned-facet prompt injection#2150

Merged
graycyrus merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/explicit-user-preferences
May 19, 2026
Merged

feat(learning): explicit user-preference tool + always-on pinned-facet prompt injection#2150
graycyrus merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/explicit-user-preferences

Conversation

@sanil-23
Copy link
Copy Markdown
Contributor

@sanil-23 sanil-23 commented May 18, 2026

Summary

  • Adds a first-class remember_preference agent tool so the model can persist user-stated preferences (e.g. "always be terse", "I use pnpm") as deterministic, pinned profile facets.
  • Adds an always-on prompt-injection path for pinned preferences via a new learning.explicit_preferences_enabled flag (default true), independent of the inference-based learning.enabled master switch (default false).
  • Three-path fetch_learned_context (both-off / explicit-only / full-learning) so explicit preferences surface even when the probabilistic learning subsystem is disabled.
  • Pure Rust core change: 9 files, ~905 insertions, 1 deletion. No frontend, no new RPC controllers, inference stack (reflection/heuristics/stability detector) untouched.

Problem

  • The learning subsystem ranks CueFamily::Explicit (a user directly stating a preference) as the highest-trust signal, but there was no deterministic path to capture it — explicit preferences were only picked up probabilistically by the LLM summariser / heuristic detectors, all gated behind learning.enabled (default off).
  • Net effect: out of the box, a user explicitly telling the agent a preference was never reliably persisted or surfaced into the prompt.

Solution

  • RememberPreferenceTool maps a user-stated preference to a (FacetClass, key, value) and upserts it as a pinned facet through the existing facet store — deterministic, idempotent (re-setting the same (class,key) overwrites; no duplicates), with no dependence on the inference engine.
  • New config flag learning.explicit_preferences_enabled (default true) + OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED env override gates an always-on UserProfileSection injection path, separate from learning.enabled.
  • Design decision/tradeoff: explicit preferences are deliberately decoupled from the stability/decay/reflection machinery — user-authoritative pinned facts should not have to "earn" stability or be subject to inference noise.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) — 16 remember_preference tests (arg/class validation, idempotent overwrite / no-duplicate edge) + 3 fetch_learned_context path tests.
  • N/A: Diff coverage ≥ 80%diff-cover/coverage tooling not runnable in this ephemeral worktree (no node_modules); new code is unit-tested and CI coverage.yml is the authoritative gate.
  • N/A: Coverage matrix updated — not updated in this PR; flagged as a follow-up TODO (new remember_preference tool row).
  • All affected feature IDs from the matrix are listed in ## RelatedN/A: no existing matrix rows touched; none to list.
  • No new external network dependencies introduced — confirmed: tool uses the existing in-process memory store; no network.
  • N/A: Manual smoke checklist updated — does not touch release-cut UI/release surfaces (core agent tool only).
  • N/A: Linked issue closed via Closes #NNN — no tracked issue; feature originated from a design working session.

Impact

  • Desktop/CLI core only. No data migration. One additive config flag (default-on, but only acts on explicitly-pinned facets, which do not exist until the tool is invoked → no behavioural change for existing users until used).
  • Security: tool writes route through the existing memory store + security wrapper; no new external surface or network deps.

Related

  • Closes: N/A — design-session origin, no tracked issue.
  • Follow-up PR(s)/TODOs: coverage-matrix row for remember_preference; broader STM / memory-pipeline work (separate WIP).

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

Linear Issue

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

Commit & Branch

  • Branch: feat/explicit-user-preferences
  • Commit SHA: 858f5bc6fd3ddef5604d183766019be476679ed3

Validation Run

  • N/A: pnpm --filter openhuman-app format:check — no frontend files changed.
  • N/A: pnpm typecheck — no frontend/TS changed.
  • Focused tests cargo test --lib -- remember_preference fetch_learned_context: 19 passed, 0 failed (7624 filtered out) — independent re-run on PR head 858f5bc6. (The literal two-positional form cargo test remember_preference fetch_learned_context is invalid cargo CLI syntax; both filters must be passed after --.)
  • Rust fmt/check (if changed): cargo check --manifest-path Cargo.toml → exit 0 (compiles on current main after rebase).
  • N/A: Tauri fmt/check — src-tauri unchanged.

Validation Blocked

  • command: git push (pre-push hook runs pnpm --filter openhuman-app format)
  • error: prettier: command not found — ephemeral worktree has no node_modules
  • impact: Frontend format hook cannot run in this worktree; change is Rust-only (0 frontend files). Pushed with --no-verify per repo policy for unrelated hook failures. CI runs full validation.

Behavior Changes

  • Intended behavior change: model can persist user-stated preferences; pinned preferences are injected into the system prompt regardless of learning.enabled.
  • User-visible effect: none until remember_preference is invoked (no pre-existing pinned facets).

Parity Contract

  • Legacy behavior preserved: inference stack (reflection/heuristics/stability) untouched; learning.enabled semantics unchanged; new path is additive and inert-by-default until the tool is used.
  • Guard/fallback/dispatch parity checks: three-path fetch_learned_context preserves prior behaviour for both-flags-off and full-learning-on.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Added a "remember preference" tool to pin user preferences across six categories; registered by default.
    • Pinned preferences are injected into the system prompt on every turn; configurable and enabled by default.
    • Context retrieval supports an "explicit-preferences only" mode that returns only pinned preferences when learning is off.
  • Chores

    • Added config/environment toggle to enable/disable explicit preference injection.
  • Tests

    • Integration tests covering preference pinning and learned-context behavior under flag combinations.

Review Change Stack

@sanil-23 sanil-23 requested a review from a team May 18, 2026 20:43
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ea1457f-5088-4b04-903e-d7060237684a

📥 Commits

Reviewing files that changed from the base of the PR and between a4f8d93 and 3769594.

📒 Files selected for processing (9)
  • src/openhuman/agent/harness/session/builder.rs
  • src/openhuman/agent/harness/session/turn.rs
  • src/openhuman/agent/harness/session/turn_tests.rs
  • src/openhuman/agent/harness/session/types.rs
  • src/openhuman/config/schema/learning.rs
  • src/openhuman/config/schema/load.rs
  • src/openhuman/tools/impl/agent/mod.rs
  • src/openhuman/tools/impl/agent/remember_preference.rs
  • src/openhuman/tools/ops.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/openhuman/tools/impl/agent/mod.rs
  • src/openhuman/tools/ops.rs
  • src/openhuman/config/schema/learning.rs
  • src/openhuman/agent/harness/session/turn.rs
  • src/openhuman/agent/harness/session/types.rs
  • src/openhuman/config/schema/load.rs
  • src/openhuman/agent/harness/session/builder.rs
  • src/openhuman/agent/harness/session/turn_tests.rs
  • src/openhuman/tools/impl/agent/remember_preference.rs

📝 Walkthrough

Walkthrough

Adds an explicit-preferences mode: config and agent wiring to enable pinned user preferences independent of learning, conditional fetch logic to return only pinned preferences when configured, a RememberPreferenceTool to persist pinned prefs, tests, and registry integration.

Changes

Explicit Preferences Feature

Layer / File(s) Summary
Configuration Schema and Environment Support
src/openhuman/config/schema/learning.rs, src/openhuman/config/schema/load.rs
New explicit_preferences_enabled: bool field added to LearningConfig with default true, and exposed via OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED environment variable for runtime override.
Agent and Builder Type Definitions
src/openhuman/agent/harness/session/types.rs
Agent and AgentBuilder structs gain explicit_preferences_enabled: bool field to carry the configuration through session construction.
Agent Builder Fluent API and Initialization
src/openhuman/agent/harness/session/builder.rs
AgentBuilder::new() defaults flag to true, new explicit_preferences_enabled(bool) fluent setter, and build() wires flag into constructed Agent.
Session Prompt Wiring and Conditional Injection
src/openhuman/agent/harness/session/builder.rs
UserProfileSection is conditionally injected into session prompt when explicit_preferences_enabled=true and learning_enabled=false; builder instantiation from config applies the configuration flag.
Learned Context Conditional Fetching
src/openhuman/agent/harness/session/turn.rs
fetch_learned_context implements three paths: empty return when both flags off, explicit-preferences-only fetch from user_profile namespace (sanitized, capped to 50) when learning disabled but explicit enabled, and full learning fetch otherwise; adds debug logs.
Learned Context Integration Tests
src/openhuman/agent/harness/session/turn_tests.rs
Integration tests verify fetch_learned_context behavior under three flag combinations with helper constructors for test agents and UnifiedMemory instances.
Remember Preference Tool with Full Test Coverage
src/openhuman/tools/impl/agent/remember_preference.rs, src/openhuman/tools/impl/agent/mod.rs
New RememberPreferenceTool with FacetClass taxonomy, deterministic pinned memory keys/content, JSON schema validation, security policy enforcement, persistence to user_profile namespace, and comprehensive tests covering validation, happy paths, idempotent overwrite, all facet classes, and security gate.
Tool Registry Integration
src/openhuman/tools/ops.rs
RememberPreferenceTool unconditionally registered in all_tools_with_runtime with shared memory and security handles.

Sequence Diagram

sequenceDiagram
  participant User
  participant RememberPreferenceTool
  participant SecurityPolicy
  participant Memory
  User->>RememberPreferenceTool: execute(class, key, value)
  RememberPreferenceTool->>SecurityPolicy: enforce_tool_operation(Act, remember_preference)
  alt Security denied
    SecurityPolicy-->>RememberPreferenceTool: error
    RememberPreferenceTool-->>User: ToolResult::error
  else Security allowed
    SecurityPolicy-->>RememberPreferenceTool: ok
    RememberPreferenceTool->>RememberPreferenceTool: validate class/key/value
    RememberPreferenceTool->>Memory: store(namespace="user_profile", key=pinned/{class}/{key}, content="[pinned] ...", MemoryCategory::Core)
    Memory-->>RememberPreferenceTool: stored
    RememberPreferenceTool-->>User: ToolResult::success
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

working

Suggested reviewers

  • senamakel
  • graycyrus

Poem

🐰 I tuck your preferences safe and neat,
Pinned in memory, steady heartbeat,
Learning rests while tastes stay true,
Small notes that guide each thing you do—
A rabbit keeps them just for you.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main changes: adding an explicit user-preference tool (RememberPreferenceTool) and enabling pinned-facet prompt injection independent of the learning system.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/openhuman/config/schema/load.rs (1)

1282-1289: ⚡ Quick win

Use parse_env_bool for this new env flag to avoid silent misconfigurations.

Line 1282 currently reimplements bool parsing and drops invalid values silently. Reusing the shared parser keeps behavior consistent and emits the warning that this file already relies on for other env flags.

Suggested patch
         if let Some(flag) = env.get("OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED") {
-            let normalized = flag.trim().to_ascii_lowercase();
-            match normalized.as_str() {
-                "1" | "true" | "yes" | "on" => self.learning.explicit_preferences_enabled = true,
-                "0" | "false" | "no" | "off" => self.learning.explicit_preferences_enabled = false,
-                _ => {}
-            }
+            if let Some(enabled) = parse_env_bool(
+                "OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED",
+                &flag,
+            ) {
+                self.learning.explicit_preferences_enabled = enabled;
+            }
         }
🤖 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/config/schema/load.rs` around lines 1282 - 1289, The block
manually parsing OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED should use the
shared parse_env_bool helper instead of reimplementing parsing and swallowing
invalid values; call parse_env_bool(env,
"OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED") (or the existing helper
signature used elsewhere) and, if it returns Some(bool), assign it to
self.learning.explicit_preferences_enabled so the same warning/logging behavior
and consistent parsing are used across the file.
src/openhuman/tools/impl/agent/remember_preference.rs (1)

305-597: ⚡ Quick win

Move the test module into a sibling remember_preference_test.rs file.

This module is already large, and the repo guidance prefers sibling Rust test files over big inline mod tests blocks for new functionality.

♻️ Proposed wiring change
-#[cfg(test)]
-mod tests {
-    // moved to remember_preference_test.rs
-}
+#[cfg(test)]
+#[path = "remember_preference_test.rs"]
+mod tests;
As per coding guidelines, "When extracting Rust tests out of an implementation file, prefer a sibling `*_test.rs` file wired in with `#[cfg(test)] #[path = \"..._test.rs\"] mod tests;`."
🤖 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/tools/impl/agent/remember_preference.rs` around lines 305 -
597, The tests in the inline mod tests block should be moved to a sibling
remember_preference_test.rs file and the implementation file should be wired to
it; extract the entire #[cfg(test)] mod tests { ... } block into that new file,
preserve all test functions (e.g., facet_class_parse_case_insensitive,
pinned_key_format, stores_preference_in_user_profile_namespace,
idempotent_overwrite_does_not_create_duplicate, stores_all_six_classes,
blocked_in_readonly_mode) and imports (NoopEmbedding, UnifiedMemory,
SecurityPolicy, TempDir, json), then in the original remember_preference.rs add
at top (or near other module-level test wiring) the line: #[cfg(test)] #[path =
"remember_preference_test.rs"] mod tests; so tests still reference symbols like
RememberPreferenceTool, FacetClass, pinned_key, PINNED_PREFERENCES_NAMESPACE and
compile against the same crate items.
🤖 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/agent/harness/session/turn.rs`:
- Around line 1273-1277: The namespace filter currently allows rows with missing
namespaces because it uses as_deref().map_or(true, |ns| ns == "user_profile");
change the predicate to require an explicit "user_profile" namespace (e.g., use
map_or(false, |ns| ns == "user_profile") or as_deref().map(|ns| ns ==
"user_profile").unwrap_or(false)) so that only entries where e.namespace ==
"user_profile" are kept; update the closure around e.namespace/as_deref() in the
filter to enforce this stricter check.

In `@src/openhuman/tools/impl/agent/remember_preference.rs`:
- Around line 175-180: The debug logs in remember_preference are currently
emitting potentially sensitive preference values (args.get("value")) verbatim;
update the tracing::debug calls inside the remember_preference tool (and the
similar debug block around the later occurrence referenced) to redact or omit
the raw value — e.g., replace the raw value with a redacted marker or log only
its presence/length (such as value.is_some() and value.as_ref().map(|v|
v.len())), keep class and key as before, and ensure any other debug that reads
args.get("value") follows the same pattern so no full PII or profile data is
written to logs.
- Around line 158-160: The validator for preference keys in
remember_preference.rs is too permissive (it currently allows uppercase letters
and '-' contrary to the schema description); update the runtime validation used
in the functions named validate_key and any related validation helpers (e.g.,
validate_keys_in_map / validate_runtime_key) to enforce the snake_case contract
by using a stricter pattern such as ^[a-z0-9_]+$ (only lowercase letters,
digits, and underscores), and adjust the error messages to explicitly state the
allowed characters so stored keys match the advertised schema.
- Around line 243-256: The extracted value currently uses value.trim() but still
allows newlines which can break the line-oriented pinned record; after getting
the string from args.get("value") (the block around args.get(...).and_then(|v|
v.as_str())), normalize it to a single-line string by replacing any \r or \n
(and collapsing consecutive whitespace) into single spaces (e.g.,
split_whitespace().join(" ")), then re-check for empty and return
ToolResult::error("value cannot be empty") if it is empty; use that sanitized
single-line string when building pinned_content(class, key, value) and
pinned_key(class, key).

---

Nitpick comments:
In `@src/openhuman/config/schema/load.rs`:
- Around line 1282-1289: The block manually parsing
OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED should use the shared
parse_env_bool helper instead of reimplementing parsing and swallowing invalid
values; call parse_env_bool(env,
"OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED") (or the existing helper
signature used elsewhere) and, if it returns Some(bool), assign it to
self.learning.explicit_preferences_enabled so the same warning/logging behavior
and consistent parsing are used across the file.

In `@src/openhuman/tools/impl/agent/remember_preference.rs`:
- Around line 305-597: The tests in the inline mod tests block should be moved
to a sibling remember_preference_test.rs file and the implementation file should
be wired to it; extract the entire #[cfg(test)] mod tests { ... } block into
that new file, preserve all test functions (e.g.,
facet_class_parse_case_insensitive, pinned_key_format,
stores_preference_in_user_profile_namespace,
idempotent_overwrite_does_not_create_duplicate, stores_all_six_classes,
blocked_in_readonly_mode) and imports (NoopEmbedding, UnifiedMemory,
SecurityPolicy, TempDir, json), then in the original remember_preference.rs add
at top (or near other module-level test wiring) the line: #[cfg(test)] #[path =
"remember_preference_test.rs"] mod tests; so tests still reference symbols like
RememberPreferenceTool, FacetClass, pinned_key, PINNED_PREFERENCES_NAMESPACE and
compile against the same crate items.
🪄 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: 7f2526f7-d616-4e04-8d36-928907f3d2d6

📥 Commits

Reviewing files that changed from the base of the PR and between 0b053c5 and 858f5bc.

📒 Files selected for processing (9)
  • src/openhuman/agent/harness/session/builder.rs
  • src/openhuman/agent/harness/session/turn.rs
  • src/openhuman/agent/harness/session/turn_tests.rs
  • src/openhuman/agent/harness/session/types.rs
  • src/openhuman/config/schema/learning.rs
  • src/openhuman/config/schema/load.rs
  • src/openhuman/tools/impl/agent/mod.rs
  • src/openhuman/tools/impl/agent/remember_preference.rs
  • src/openhuman/tools/ops.rs

Comment thread src/openhuman/agent/harness/session/turn.rs
Comment thread src/openhuman/tools/impl/agent/remember_preference.rs
Comment thread src/openhuman/tools/impl/agent/remember_preference.rs
Comment thread src/openhuman/tools/impl/agent/remember_preference.rs Outdated
@sanil-23
Copy link
Copy Markdown
Contributor Author

Test-wiring investigation results

This comment reports the findings from investigating the two alleged test-wiring defects on feat/explicit-user-preferences (HEAD 858f5bc6).


Defect 1: remember_preference tests not running

Finding: already wired and passing. No code change needed.

Root cause of the false "0 tests" diagnosis: the investigation used cargo test --manifest-path Cargo.toml remember_preference (no --lib flag). Without --lib, cargo dispatches the filter to each compiled test binary individually — and the integration-test binaries (json_rpc_e2e, observability_smoke, etc.) all report "0 tests … N filtered out" for remember_preference because none of those binaries contain that test. The lib binary was compiled and run, but its results were buried in the multi-binary output where the grep missed them.

Correct invocation:

cargo test --manifest-path Cargo.toml --lib -- remember_preference

Actual result:

running 16 tests
test openhuman::tools::implementations::agent::remember_preference::tests::blocked_in_readonly_mode ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::empty_key_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::facet_class_as_str_round_trips ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::facet_class_parse_case_insensitive ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::idempotent_overwrite_does_not_create_duplicate ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::invalid_class_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::key_with_spaces_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::missing_class_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::missing_key_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::missing_value_returns_error ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::pinned_content_format ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::pinned_key_format ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::schema_has_required_fields ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::stores_all_six_classes ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::stores_preference_in_user_profile_namespace ... ok
test openhuman::tools::implementations::agent::remember_preference::tests::tool_name_and_permission ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 7627 filtered out; finished in 0.21s

The wiring is correct: src/openhuman/tools/mod.rs uses #[path = "impl/mod.rs"] pub(crate) mod implementations; which aliases the impl/ directory, and impl/agent/mod.rs correctly declares pub mod remember_preference;. The #[cfg(test)] mod tests inside remember_preference.rs compiles and runs as openhuman::tools::implementations::agent::remember_preference::tests::*.


Defect 2: turn_tests.rs orphaned file

Finding: already wired and passing. No code change needed.

The claim was that mod turn_tests; is declared nowhere. Checking src/openhuman/agent/harness/session/turn.rs at lines 1910-1912:

#[cfg(test)]
#[path = "turn_tests.rs"]
mod tests;

turn.rs declares turn_tests.rs as its inline test module via the #[path] attribute. It compiles as openhuman::agent::harness::session::turn::tests::*. The 3 fetch_learned_context_* tests are NOT orphaned.

Actual result:

running 3 tests
test openhuman::agent::harness::session::turn::tests::fetch_learned_context_explicit_flag_off_learning_off_returns_empty_even_with_stored_prefs ... ok
test openhuman::agent::harness::session::turn::tests::fetch_learned_context_returns_empty_when_both_flags_off ... ok
test openhuman::agent::harness::session::turn::tests::fetch_learned_context_returns_pinned_prefs_when_explicit_flag_on_learning_off ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 7640 filtered out; finished in 0.14s

Summary

Both groups of tests — all 16 remember_preference tests and all 3 fetch_learned_context tests — compile and pass on HEAD 858f5bc6. No source changes were required. The reported defects were diagnosis artifacts from incorrect test invocation (missing --lib) and a #[path] attribute that wasn't noticed during the initial inspection. No new commit or push needed.

sanil-23 added a commit to sanil-23/openhuman that referenced this pull request May 19, 2026
…act + single-line preference value (tinyhumansai#2150)

- turn.rs: namespace filter now uses map_or(false, …) so entries with a
  missing namespace are excluded; only entries where namespace == "user_profile"
  pass through, preventing non-user_profile rows leaking into the profile context.

- remember_preference.rs: tightened key validator to accept only [a-z0-9_]
  (lowercase snake_case); uppercase letters and hyphens are now rejected with
  a clear error message consistent with the existing validation style.

- remember_preference.rs: both debug log statements that previously emitted
  the raw preference value (PII risk) now log only value_present/value_len;
  the key label is retained as it is non-sensitive.

- remember_preference.rs: value is normalized to a single line before being
  passed to pinned_content(); embedded CR/LF are replaced with a space and
  the result is trimmed, preventing newline-injection into the line-oriented
  [pinned] key: value storage format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 19, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Walkthrough

Clean, well-structured feature addition. Adds a deterministic remember_preference tool for pinning explicit user preferences, plus an always-on injection path via explicit_preferences_enabled (default true). The three-path fetch_learned_context dispatch is a good design — explicit preferences surface even when the probabilistic learning subsystem is off. 19 tests cover validation, happy path, idempotency, security gate, and all three fetch paths. CodeRabbit's 4 actionable findings were all addressed in the fix commit.

Change Summary

File Change type Description
tools/impl/agent/remember_preference.rs New RememberPreferenceTool — parses (class, key, value), validates, upserts to user_profile namespace as pinned facet. 16 tests.
agent/harness/session/turn.rs Modified fetch_learned_context → 3-path dispatch: both-off (empty), explicit-only (pinned prefs), full learning
agent/harness/session/turn_tests.rs Modified 3 integration tests for the 3 fetch paths, using real UnifiedMemory
agent/harness/session/builder.rs Modified Wires explicit_preferences_enabled through builder + registers UserProfileSection when explicit-only
agent/harness/session/types.rs Modified Adds explicit_preferences_enabled field to Agent and AgentBuilder
config/schema/learning.rs Modified New explicit_preferences_enabled config field with serde default + doc
config/schema/load.rs Modified Env var override OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED
tools/impl/agent/mod.rs Modified Module declaration + re-export
tools/ops.rs Modified Registers RememberPreferenceTool in all_tools_with_runtime

Per-file analysis

remember_preference.rs — Well-written. FacetClass enum with parse/round-trip, clean validation flow, proper security gate, good error messages. The PII redaction and single-line normalization fixes from the second commit are solid.

turn.rs — Three-path dispatch is clear and well-commented. Two minor observations below.

builder.rs — Correctly gates UserProfileSection registration on explicit_preferences_enabled && !learning.enabled to avoid double-registration when full learning is active.

load.rs — Env var parsing works but deviates from the file's pattern (see CodeRabbit nitpick #5parse_env_bool used everywhere else).

Overall: solid work. Only minor nits below — no blockers.

Comment thread src/openhuman/agent/harness/session/turn.rs
Comment thread src/openhuman/agent/harness/session/turn.rs
sanil-23 and others added 3 commits May 19, 2026 18:11
…t prompt injection

Add `remember_preference` agent tool so the LLM can persist explicit
user preferences (style, identity, tooling, veto, goal, channel) to
the `user_profile` memory namespace as keyed pinned entries, independent
of the full inference-based learning subsystem.

Add `learning.explicit_preferences_enabled` config flag (default `true`,
env override `OPENHUMAN_LEARNING_EXPLICIT_PREFERENCES_ENABLED`) that
injects a `UserProfileSection` into the system prompt and fetches
pinned preferences on every turn even when `learning.enabled=false`.

The `fetch_learned_context` narrow path uses `memory.get()` semantics
(via the `list` + key-prefix filter) for deduplication checks, and
`memory.get()` directly for content assertions in tests, avoiding the
`list()` → `title` column mismatch in the unified memory backend.

16 unit tests cover arg validation, class parsing, upsert idempotency,
and all six facet classes. Three turn tests cover the three flag
combinations (both off, explicit-only, both on).
…act + single-line preference value (tinyhumansai#2150)

- turn.rs: namespace filter now uses map_or(false, …) so entries with a
  missing namespace are excluded; only entries where namespace == "user_profile"
  pass through, preventing non-user_profile rows leaking into the profile context.

- remember_preference.rs: tightened key validator to accept only [a-z0-9_]
  (lowercase snake_case); uppercase letters and hyphens are now rejected with
  a clear error message consistent with the existing validation style.

- remember_preference.rs: both debug log statements that previously emitted
  the raw preference value (PII risk) now log only value_present/value_len;
  the key label is retained as it is non-sensitive.

- remember_preference.rs: value is normalized to a single line before being
  passed to pinned_content(); embedded CR/LF are replaced with a space and
  the result is trimmed, preventing newline-injection into the line-oriented
  [pinned] key: value storage format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d-pref truncation (tinyhumansai#2150)

- turn.rs: Rewrote the misleading comment on the `.filter()` block.  The
  previous comment claimed "list without namespace filter would return
  everything", but `.list(Some("user_profile"), ...)` already scopes at
  the store layer.  The new comment makes clear that `.list()` provides
  the real scoping and the `.filter()` is a belt-and-suspenders defensive
  guard against any future store-layer change.

- turn.rs: Added `tracing::warn!` (with structured `total` / `dropped`
  fields) when `profile_entries.len() > 50` so silent truncation by
  `.take(50)` is visible in debug logs.  The warn fires before the
  iterator chain and logs only the count — no preference values or PII
  are emitted, consistent with the redaction fix from the prior commit.

Addresses graycyrus review comments #3265703537 and #3265703543.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Looks good, nice work!

@graycyrus graycyrus merged commit d4dacd6 into tinyhumansai:main May 19, 2026
32 of 50 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…t prompt injection (tinyhumansai#2150)

Co-authored-by: Gray Cyrus <cyrus@tinyhumans.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants