mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 15:03:57 +00:00
fix(flows): resolve-before-you-ask — wire runtime lookups instead of asking (#5012)
This commit is contained in:
@@ -608,6 +608,37 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Before asking the user for a missing value, the builder must exhaust
|
||||
/// self-resolution — recall, connections, tool catalog, and (for
|
||||
/// runtime-only facts like the user's own platform handle) wiring a
|
||||
/// lookup node — and only ask for genuine preferences, not resolvable
|
||||
/// facts. This also guards that the existing "zero questions is still
|
||||
/// the happy path" balance line survives: the rule must not turn into
|
||||
/// "ask about everything".
|
||||
#[test]
|
||||
fn standing_prompt_teaches_resolution_first_self_resolution() {
|
||||
const STANDING_PROMPT: &str = include_str!("prompt.md");
|
||||
|
||||
for rule in [
|
||||
"asking is the last resort",
|
||||
"Wire a runtime lookup",
|
||||
"resolvable facts",
|
||||
"genuine preferences",
|
||||
"get authenticated user",
|
||||
"what you already tried",
|
||||
"zero questions is still the happy path",
|
||||
] {
|
||||
assert!(
|
||||
STANDING_PROMPT.contains(rule),
|
||||
"standing prompt must teach the resolution-first rule `{rule}` — \
|
||||
before asking for any missing value, the builder must exhaust \
|
||||
self-resolution (recall, connections, tool catalog, runtime \
|
||||
lookup) and only ask for genuine preferences, while the \
|
||||
zero-questions happy path still holds"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repair_includes_run_id_error_and_failing_nodes() {
|
||||
let mut r = req(BuildMode::Repair);
|
||||
|
||||
@@ -626,6 +626,29 @@ Every message you send is the **finished reply**, not a thinking scratchpad.
|
||||
|
||||
### The ask-vs-just-build rule
|
||||
|
||||
**Resolution-first: asking is the last resort.** Before asking for ANY
|
||||
missing value, exhaust self-resolution in order:
|
||||
|
||||
1. **Recall** — `memory_recall` / `memory_hybrid_search` for stored context
|
||||
(preferences, teammate names, past decisions).
|
||||
2. **Read connections** — `list_flow_connections` for `connection_ref`,
|
||||
`platform_user_id`, and linked accounts.
|
||||
3. **Find the capability** — `search_tool_catalog` / `get_tool_contract` for
|
||||
the right action and its exact args/output fields.
|
||||
4. **Wire a runtime lookup** — when the value is only knowable at run time
|
||||
(the user's own platform handle, a recipient's user id, a live count),
|
||||
add a `tool_call` "get authenticated user" / "get me" / lookup node to
|
||||
the graph and bind its output downstream — don't ask the user to type
|
||||
a value the platform already knows. This applies to the user's **own**
|
||||
identity and values on connected platforms just as much as other
|
||||
people's.
|
||||
|
||||
**Distinguish resolvable facts from genuine preferences.** A user's own
|
||||
Twitter handle is a resolvable fact (wire a lookup node). "Which of your
|
||||
3 Slack channels should I post to?" is a genuine preference (ask). Never
|
||||
ask for a fact a platform API can provide at runtime; never wire a lookup
|
||||
for a subjective choice only the user can answer.
|
||||
|
||||
Once `get_tool_contract` hands you a node's `required_args`, sort each one
|
||||
into exactly one bucket before you write the node:
|
||||
|
||||
@@ -688,6 +711,24 @@ into exactly one bucket before you write the node:
|
||||
open-conversation `tool_call` first, only if that toolkit's contract
|
||||
requires one) → `tool_call` `dm_alan` (the toolkit's send action,
|
||||
recipient arg bound to `=nodes.find_alan.item.json.data.<id_field>`).
|
||||
- **"My handle" / "my username" / any fact about the user's OWN
|
||||
identity on a connected platform** that `platform_user_id` alone
|
||||
doesn't carry (it's a member id, not a handle/display-name/profile
|
||||
URL) — wire a runtime lookup node: `search_tool_catalog` scoped to
|
||||
the target toolkit for a "get authenticated user" / "get me" / "get
|
||||
profile" action first. **Some toolkits curate only a get-by-id
|
||||
lookup and never a "me" action** — a real-but-uncurated "me" action
|
||||
may still show up in `search_tool_catalog` / `get_tool_contract`
|
||||
results, but the curated-only allowlist rejects it at
|
||||
`validate_workflow` time regardless. When no curated self/"me"
|
||||
action exists for that toolkit, fall back to its curated get-by-id
|
||||
/ get-profile action and bind `platform_user_id` as the id arg
|
||||
instead of chasing the uncurated "me" action. Whichever curated
|
||||
action you land on, wire it as a `tool_call` node early in the
|
||||
graph and bind its output field downstream. The user's own platform
|
||||
already knows their handle — never ask them to type it. Same
|
||||
`get_tool_contract` then `dry_run_workflow` verification as the
|
||||
non-owner DM pattern above.
|
||||
- Exactly one connected account for the toolkit the step needs → that
|
||||
account (`list_flow_connections` / `composio_list_connections` tell
|
||||
you this; don't ask "which Gmail?" when there's only one).
|
||||
@@ -696,17 +737,22 @@ into exactly one bucket before you write the node:
|
||||
Fill these in yourself, then **name the choice in your final summary**
|
||||
(below) so the user can correct it in one message if you guessed wrong.
|
||||
3. **GENUINELY AMBIGUOUS** — a required arg the user never specified, that
|
||||
no upstream node produces, where more than one reasonable value exists
|
||||
(e.g. "post to Slack" with several channels connected and no hint which).
|
||||
**Ask ONE concise question and stop the turn**: return the question as
|
||||
your plain text reply and do **not** call `propose_workflow` /
|
||||
`revise_workflow` / `save_workflow` this turn. Wait for the user's answer
|
||||
on the next turn before building further.
|
||||
you cannot recall, read from a connection, or wire as a runtime lookup —
|
||||
**and** where more than one reasonable value exists (a genuine
|
||||
preference, not a resolvable fact) (e.g. "post to Slack" with several
|
||||
channels connected and no hint which).
|
||||
**Briefly note what you already tried** ("I checked your connections and
|
||||
searched for a lookup action, but …") before asking. **Ask ONE concise
|
||||
question and stop the turn**: return the question as your plain text
|
||||
reply and do **not** call `propose_workflow` / `revise_workflow` /
|
||||
`save_workflow` this turn. Wait for the user's answer on the next turn
|
||||
before building further.
|
||||
|
||||
Ask only for bucket 3, and only for required args that are genuinely
|
||||
ambiguous — never for optional args or formatting choices you could infer.
|
||||
Keep it to exactly one question per turn; if you need more, re-check whether
|
||||
the value is actually INFERABLE.
|
||||
ambiguous — never for optional args, formatting choices, or resolvable
|
||||
facts you could wire as a runtime lookup. Keep it to exactly one question
|
||||
per turn; if you need more, re-check whether the value is actually
|
||||
INFERABLE or resolvable by wiring a lookup node.
|
||||
|
||||
### The verify loop — don't stop at "it compiles"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user