Graphify-Labs/graphify · error · ValueError
outcome must be one of {OUTCOMES}, got {outcome!r}
Error message
outcome must be one of {OUTCOMES}, got {outcome!r} What it means
Raised by graphify's Q&A memory writer (save query/Q&A results as markdown in the memory dir) when the optional outcome argument is non-None but not one of the allowed tuple OUTCOMES = ('useful', 'dead_end', 'corrected'). The outcome is a work-memory signal written to both the YAML frontmatter and an '## Outcome' body section, so only the three canonical values round-trip deterministically through `graphify reflect`.
Source
Thrown at graphify/ingest.py:295
memory_dir: Path,
query_type: str = "query",
source_nodes: list[str] | None = None,
outcome: str | None = None,
correction: str | None = None,
) -> Path:
"""Save a Q&A result as markdown so it gets extracted into the graph on next --update.
Files are stored in memory_dir (typically graphify-out/memory/) with YAML frontmatter
that graphify's extractor reads as node metadata. This closes the feedback loop:
the system grows smarter from both what you add AND what you ask.
``outcome`` (one of :data:`OUTCOMES`) and ``correction`` are optional work-memory
signals: they are written both to the frontmatter (so `graphify reflect` can
aggregate them deterministically) and to an ``## Outcome`` body section (so the
signal round-trips into the graph on the next semantic re-extraction).
"""
if outcome is not None and outcome not in OUTCOMES:
raise ValueError(f"outcome must be one of {OUTCOMES}, got {outcome!r}")
memory_dir = Path(memory_dir)
memory_dir.mkdir(parents=True, exist_ok=True)
now = datetime.now(timezone.utc)
slug = re.sub(r"[^\w]", "_", question.lower())[:50].strip("_")
filename = f"query_{now.strftime('%Y%m%d_%H%M%S')}_{slug}.md"
frontmatter_lines = [
"---",
f'type: "{query_type}"',
f'date: "{now.isoformat()}"',
f'question: "{_yaml_str(question)}"',
'contributor: "graphify"',
]
if outcome:
frontmatter_lines.append(f'outcome: "{_yaml_str(outcome)}"')
if correction:View on GitHub (pinned to 7fe58b0b0f)
Solutions
- Use one of the exact values: 'useful', 'dead_end', or 'corrected' (lowercase, underscore)
- Pass None (or omit the argument) when there is no outcome signal
- Map external enums at the call site: {'positive': 'useful', 'negative': 'dead_end', 'fix': 'corrected'}
Example fix
# before
save_qa("How do hooks work?", answer, outcome="helpful")
# ValueError: outcome must be one of ('useful', 'dead_end', 'corrected')
# after
save_qa("How do hooks work?", answer, outcome="useful") Defensive patterns
Strategy: type-guard
Validate before calling
from graphify.ingest import OUTCOMES # ('useful', 'dead_end', 'corrected')
if outcome is not None and outcome not in OUTCOMES:
raise SystemExit(f"invalid outcome {outcome!r}; choose from {OUTCOMES}") Type guard
from typing import Literal
from graphify.ingest import OUTCOMES
Outcome = Literal["useful", "dead_end", "corrected"]
def is_valid_outcome(value: object) -> bool:
"""Narrow arbitrary input to the canonical outcome tuple."""
return value is None or (isinstance(value, str) and value in OUTCOMES) Try / catch
try:
save_qa(question, answer, outcome=outcome)
except ValueError as e:
if "outcome must be one of" in str(e):
outcome = "corrected" if correction else "useful" # sanitize to a default
save_qa(question, answer, outcome=outcome, correction=correction)
else:
raise Prevention
- Import OUTCOMES from graphify.ingest and validate against the tuple - don't hardcode the list
- Type your call sites with Literal['useful','dead_end','corrected'] so bad values fail static checks
- Map external enums/labels to the canonical trio at the system boundary
When it happens
Trigger: Calling the save-QA function (e.g. graphify's API for recording query outcomes) with outcome='yes', 'bad', 'USEFUL' (case-sensitive), or any free-form string outside the tuple; None is explicitly allowed.
Common situations: Scripting the memory loop with ad-hoc labels; UI wrappers mapping their own enum ('positive'/'negative') onto outcome; case mismatches; passing 0/1 integers.
Related errors
- Google Workspace shortcut {path} does not include a Drive fi
- ingest: {exc}
- Unknown backend {backend!r}. Available: {sorted(BACKENDS)}
- token_budget must be positive, got {token_budget}
- Unknown backend {backend!r}
AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14).
Data as JSON: /api/errors/c62ff04bbca1c513.
Report an issue: GitHub.