mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(orchestrator): prefer live integrations over memory_tree for inbox/doc queries (#1731)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Rafael Figuereo <rafaelfiguereod@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Rafael Figuereo
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
1c58d478c5
commit
4d5e0d5aad
Generated
+203
-201
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -45,7 +45,7 @@ env_logger = "0.11"
|
||||
base64 = "0.22"
|
||||
aes-gcm = "0.10"
|
||||
argon2 = "0.5"
|
||||
rand = "0.9"
|
||||
rand = "0.10"
|
||||
dirs = "5"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
@@ -110,7 +110,7 @@ tower = { version = "0.5", default-features = false }
|
||||
opentelemetry = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry_sdk = { version = "0.31", default-features = false, features = ["trace", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.31", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
||||
sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "debug-images", "reqwest", "rustls", "test"] }
|
||||
sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "debug-images", "reqwest", "rustls"] }
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
url = "2"
|
||||
socketioxide = { version = "0.15", features = ["extensions"] }
|
||||
|
||||
Generated
+357
-319
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
|
||||
export const DEFAULT_PORT = 18473;
|
||||
export const MOCK_JWT = "e2e-mock-jwt-token";
|
||||
export const MAX_PORT_RETRY_ATTEMPTS = 10;
|
||||
export const MAX_MOCK_DELAY_MS = 30_000;
|
||||
|
||||
let requestLog = [];
|
||||
let mockBehavior = {};
|
||||
@@ -59,7 +60,8 @@ export function parseBehaviorJson(key, fallback) {
|
||||
|
||||
export function getDelayMs(key) {
|
||||
const value = Number(mockBehavior[key] || 0);
|
||||
return Number.isFinite(value) && value > 0 ? value : 0;
|
||||
if (!Number.isFinite(value) || value <= 0) return 0;
|
||||
return Math.min(value, MAX_MOCK_DELAY_MS);
|
||||
}
|
||||
|
||||
export function sleep(ms) {
|
||||
|
||||
+6
-3
@@ -178,10 +178,13 @@ pub async fn rpc_auth_middleware(req: axum::extract::Request, next: Next) -> Res
|
||||
///
|
||||
/// Uses `rand::rng()` (thread-local, OS-seeded CSPRNG) introduced in rand 0.9.
|
||||
fn generate_token() -> String {
|
||||
use rand::RngCore as _;
|
||||
use rand::RngExt as _;
|
||||
log::trace!("[auth] generate_token: start (32 bytes)");
|
||||
let mut bytes = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut bytes);
|
||||
hex::encode(bytes)
|
||||
rand::rng().fill(&mut bytes);
|
||||
let token = hex::encode(bytes);
|
||||
log::trace!("[auth] generate_token: complete (64 hex chars)");
|
||||
token
|
||||
}
|
||||
|
||||
/// Write `token` to `path` with owner-only read+write permissions on Unix.
|
||||
|
||||
@@ -17,19 +17,23 @@ Follow this sequence for every user message:
|
||||
1. **Can I answer directly without tools?**
|
||||
- Yes: reply directly (small talk, simple Q&A, basic factual answers).
|
||||
- No: continue.
|
||||
2. **Can I solve this with direct tools?**
|
||||
- Yes: use direct tools first (`current_time`, `cron_*`, `memory_*`, `composio_list_connections`, etc.).
|
||||
2. **Does the request name (or imply) a connected external service?**
|
||||
- Words like "email/inbox/gmail", "calendar", "notion doc", "drive file", "slack/whatsapp/telegram message", "linear ticket", "send to X", "check X", etc. mean the user wants the **live** service.
|
||||
- Find the matching toolkit in the **Connected Integrations** section and call `delegate_to_integrations_agent` with that `toolkit`.
|
||||
- **Do this even if `memory_tree` could plausibly answer.** The user wants the live source of truth, not a stale summary. Use `memory_tree` only when the user explicitly asks about historical/ingested context (e.g. "what did we discuss last month", "summarise my recent activity") or when a live lookup just failed.
|
||||
- If the relevant toolkit is not in **Connected Integrations**, tell the user to connect it via Settings → Connections → [Service] (see "Connecting external services" below). Do **not** silently fall back to `memory_tree`.
|
||||
3. **Can I solve this with direct tools?**
|
||||
- Yes: use direct tools (`current_time`, `cron_*`, `memory_*`, `composio_list_connections`, etc.).
|
||||
- No: continue.
|
||||
3. **Does this need specialised execution?**
|
||||
- If external SaaS integration work is required, use `delegate_to_integrations_agent` with `toolkit` set to the relevant slug (see the **Connected Integrations** section for the current list).
|
||||
4. **Does this need other specialised execution?**
|
||||
- If code writing/execution/debugging is required, use `delegate_run_code`.
|
||||
- If web/doc crawling is required, use `delegate_researcher`.
|
||||
- If complex multi-step decomposition is required, use `delegate_plan`.
|
||||
- If code review is requested, use `delegate_critic`.
|
||||
- If memory archiving or distillation is required, use `delegate_archivist`.
|
||||
4. **After delegation**, summarise results clearly and concisely.
|
||||
5. **After delegation**, summarise results clearly and concisely.
|
||||
|
||||
Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient**. Use `spawn_worker_thread` for long tasks that need their own thread.
|
||||
Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient** — but a live external-service request is *not* something to answer from memory, it requires the integration. Use `spawn_worker_thread` for long tasks that need their own thread.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -108,19 +112,33 @@ checking notion
|
||||
|
||||
"Q2 roadmap" — 3 bullets: ship auth, cut v0.4, hire designer
|
||||
```
|
||||
(`delegate_to_integrations_agent` with `toolkit: "notion"`. The user wants the live doc, not a memory summary.)
|
||||
|
||||
User: any new emails from alice today?
|
||||
→
|
||||
```text
|
||||
checking gmail
|
||||
|
||||
one, 2pm: "lunch friday?", wants to grab food, no agenda
|
||||
```
|
||||
(`delegate_to_integrations_agent` with `toolkit: "gmail"`. Do **not** start with `memory_tree`; the user is asking about live inbox state.)
|
||||
|
||||
Short answers can skip the ack:
|
||||
|
||||
User: what time is it?
|
||||
→ `7:31pm`
|
||||
|
||||
## Memory tree retrieval
|
||||
## Memory tree retrieval (historical context only)
|
||||
|
||||
Use `memory_tree` with a `mode` argument to query the user's ingested email/chat/document history:
|
||||
`memory_tree` queries the user's **already-ingested** email/chat/document history. It is a retrospective index, **not** a live API for connected services. If the user is asking what's in their inbox / calendar / docs *right now*, use `delegate_to_integrations_agent` instead (step 2 of the decision tree).
|
||||
|
||||
- `mode: "search_entities"` — resolve a name to a canonical id (e.g. "alice" → `email:alice@example.com`). ALWAYS call this first when the user mentions someone by name.
|
||||
Reach for `memory_tree` when the user asks about prior context that's already been summarised — "what did Alice and I discuss last month", "summarise my recent activity", "remind me what we decided on Q2 roadmap" — or when a live integration call has just failed and a stale answer is still useful.
|
||||
|
||||
Modes:
|
||||
|
||||
- `mode: "search_entities"` — resolve a name to a canonical id (e.g. "alice" → `email:alice@example.com`). Call this first when the user mentions someone by name *and* you've decided memory_tree is the right tool.
|
||||
- `mode: "query_topic"` — all cross-source mentions of an `entity_id` from `search_entities`.
|
||||
- `mode: "query_source"` — filter by `source_kind` (chat/email/document) and `time_window_days`. Use for "in my email last week…" intents.
|
||||
- `mode: "query_source"` — filter by `source_kind` (chat/email/document) and `time_window_days`. Use for retrospective "in my email last week…" intents — **not** for live "check my inbox" intents.
|
||||
- `mode: "query_global"` — cross-source daily digest over `time_window_days` (7-day digest is pre-loaded into context on session start — only call for a different window or to force refresh).
|
||||
- `mode: "drill_down"` — expand a coarse `node_id` summary one level.
|
||||
- `mode: "fetch_leaves"` — pull raw `chunk_ids` for citation.
|
||||
|
||||
@@ -158,8 +158,12 @@ mod tests {
|
||||
let body = build(&ctx_with(&[])).unwrap();
|
||||
assert!(body.contains("## Delegation Decision Tree (Direct-First)"));
|
||||
assert!(body.contains(
|
||||
"Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient**."
|
||||
"Default bias: **do not spawn a sub-agent when a direct response or direct tool call is sufficient**"
|
||||
));
|
||||
// Step 2 of the decision tree now explicitly routes live external-service
|
||||
// requests to `delegate_to_integrations_agent` rather than `memory_tree`.
|
||||
assert!(body.contains("Does the request name (or imply) a connected external service?"));
|
||||
assert!(body.contains("Do this even if `memory_tree` could plausibly answer"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -107,9 +107,8 @@ fn new_tree_id(kind: TreeKind) -> String {
|
||||
/// sized for uniqueness across the file-system and Obsidian wikilink
|
||||
/// namespaces.
|
||||
pub fn new_summary_id(level: u32) -> String {
|
||||
use rand::Rng;
|
||||
let ms = chrono::Utc::now().timestamp_millis() as u64;
|
||||
let rand_tail: u32 = rand::thread_rng().gen();
|
||||
let rand_tail: u32 = rand::random();
|
||||
format!("summary:{:013}:L{}-{:08x}", ms, level, rand_tail)
|
||||
}
|
||||
|
||||
|
||||
@@ -209,9 +209,9 @@ fn generate_code() -> String {
|
||||
/// on macOS). The 32 random bytes (256 bits) are hex-encoded for a
|
||||
/// 64-character token, providing 256 bits of entropy.
|
||||
fn generate_token() -> String {
|
||||
use rand::RngCore;
|
||||
use rand::RngExt as _;
|
||||
let mut bytes = [0u8; 32];
|
||||
rand::rng().fill_bytes(&mut bytes);
|
||||
rand::rng().fill(&mut bytes);
|
||||
format!("zc_{}", hex::encode(bytes))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -25,7 +25,7 @@ impl Default for HumanPathOptions {
|
||||
}
|
||||
|
||||
/// Returns `(x, y, dwell_ms)` steps for a humanized cursor path.
|
||||
pub fn human_path<R: Rng>(
|
||||
pub fn human_path<R: RngExt>(
|
||||
start: (i32, i32),
|
||||
end: (i32, i32),
|
||||
opts: &HumanPathOptions,
|
||||
@@ -95,7 +95,7 @@ fn cubic_bezier(
|
||||
)
|
||||
}
|
||||
|
||||
fn dwell_ms<R: Rng>(opts: &HumanPathOptions, rng: &mut R) -> u64 {
|
||||
fn dwell_ms<R: RngExt>(opts: &HumanPathOptions, rng: &mut R) -> u64 {
|
||||
let mean = finite_or_default(opts.mean_step_ms, HumanPathOptions::default().mean_step_ms);
|
||||
let stddev = finite_or_default(
|
||||
opts.stddev_step_ms,
|
||||
@@ -119,7 +119,7 @@ fn finite_or_default(value: f64, default: f64) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_normal<R: Rng>(mean: f64, stddev: f64, rng: &mut R) -> f64 {
|
||||
fn sample_normal<R: RngExt + RngExt>(mean: f64, stddev: f64, rng: &mut R) -> f64 {
|
||||
if stddev <= 0.0 {
|
||||
return mean;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ fn sample_normal<R: Rng>(mean: f64, stddev: f64, rng: &mut R) -> f64 {
|
||||
.random::<f64>()
|
||||
.clamp(f64::MIN_POSITIVE, 1.0 - f64::EPSILON);
|
||||
let u2 = rng.random::<f64>();
|
||||
let z0 = (-2.0 * u1.ln()).sqrt() * (TAU * u2).cos();
|
||||
let z0 = (-2.0_f64 * u1.ln()).sqrt() * (TAU * u2).cos();
|
||||
mean + z0 * stddev
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user