HKUDS/Vibe-Trading · error · ValueError
source.variables must be an object
Error message
source.variables must be an object
What it means
For playbook sources, source.variables (used by playbook.render) must be a JSON object/Mapping when provided. Arrays, strings, or scalars are rejected because template variables are keyed.
Source
Thrown at agent/src/scheduled_research/service.py:146
raise ValueError("source and schedule must be objects")
if not isinstance(delivery_spec, Mapping):
raise ValueError("delivery must be an object")
source_type = str(source.get("kind") or "prompt")
playbook_slug = None
config: dict[str, Any] = {}
if source_type == "prompt":
prompt = str(source.get("prompt") or "").strip()
if not prompt:
raise ValueError("source.prompt is required")
elif source_type == "playbook":
playbook_slug = str(source.get("playbook_slug") or "").strip()
if not playbook_slug:
raise ValueError("source.playbook_slug is required")
playbook = get_playbook(playbook_slug)
variables = source.get("variables") or {}
if not isinstance(variables, Mapping):
raise ValueError("source.variables must be an object")
prompt = playbook.render(variables)
config["playbook"] = playbook_slug
else:
raise ValueError("source.kind must be 'prompt' or 'playbook'")
expression = str(schedule_spec.get("expression") or "").strip()
timezone = schedule_spec.get("timezone")
validate_schedule(expression)
if is_interval_schedule(expression):
validate_timezone_shape(timezone)
else:
validate_timezone(timezone)
next_run_at = now
if timezone is not None and not is_interval_schedule(expression):
next_run_at = next_due(expression, now, timezone)
end_at = _parse_end_at(draft.get("end_at"))
if end_at is not None and end_at <= now:View on GitHub (pinned to 80ffdda44c)
Solutions
- Send variables as an object: {"topic": "AI", "depth": 3}
- Omit it or pass null to use no variables
- If the string case is double-encoding, JSON.parse it client-side before submit
Example fix
// before
{"variables": "{\"topic\": \"AI\"}"}
// after
{"variables": {"topic": "AI"}} Defensive patterns
Strategy: type-guard
Validate before calling
from collections.abc import Mapping
v = draft.get("source", {}).get("variables")
assert v is None or isinstance(v, Mapping) Type guard
from collections.abc import Mapping
def variables_valid(src: dict) -> bool:
v = src.get("variables")
return v is None or isinstance(v, Mapping) Try / catch
try:
propose_create(draft)
except ValueError as e:
if "variables must be an object" in str(e):
draft["source"]["variables"] = json.loads(draft["source"]["variables"]) if isinstance(draft["source"].get("variables"), str) else {} Prevention
- Avoid double-JSON-encoding variables
- Build variables as dicts in client code, not serialized strings
When it happens
Trigger: Sending variables as a list of pairs, a JSON-encoded string, or null-adjacent scalars (note: null/absent defaults to {}).
Common situations: Double-serialized JSON (variables: '{"k":1}'); clients mapping form arrays into the field; API glue code passing **kwargs lists.
Related errors
- playbook {self.slug!r} has no variable {key!r}; declared: {s
- playbook variable {key!r} is {len(text)} chars; the cap is {
- source.playbook_slug is required
- timezone {tz!r} is not a recognized IANA timezone key
- unknown delivery status {raw_status!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/94c31384e550e14d.
Report an issue: GitHub.