{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7775f7c0",
   "metadata": {},
   "source": [
    "# Language → Plan (GoldenRetriever)\n",
    "\n",
    "Turn a scene **caption** into a small primitive **plan** using three plain\n",
    "Flows. There is no LLM in the loop — the planner is deterministic rules, so the\n",
    "notebook runs anywhere and shows the real point: the **typed data contract**\n",
    "between language and planning, and where a model would later slot in."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06c7a2e3",
   "metadata": {},
   "source": [
    "> **Running in Colab?** The next cell installs `retriever-core`. From a source\n",
    "> checkout (or once it's already installed) the install is skipped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e615b91",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Colab setup: install retriever-core only if it isn't importable yet.\n",
    "try:\n",
    "    import retriever  # noqa: F401\n",
    "except ImportError:  # pragma: no cover\n",
    "    import subprocess\n",
    "    import sys\n",
    "\n",
    "    subprocess.run(\n",
    "        [sys.executable, \"-m\", \"pip\", \"install\", \"retriever-core\"], check=True\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "398d369e",
   "metadata": {},
   "source": [
    "## The Flows\n",
    "\n",
    "Each stage is a `Flow[In, Out]` whose payloads are standard language types from\n",
    "`retriever.types.language`. A Flow wakes on its clock and returns one typed\n",
    "record — it knows nothing about buffering or how it's wired."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7458f892",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "from retriever.flow import Flow\n",
    "from retriever.types.language import Caption, PlanStepText, PlanText\n",
    "\n",
    "\n",
    "class CaptionSource(Flow[None, Caption]):\n",
    "    \"\"\"Emits one caption per tick (stands in for a captioner / VLM).\"\"\"\n",
    "\n",
    "    def __init__(self, *, captions=None):\n",
    "        super().__init__()\n",
    "        self.captions = tuple(captions or (\n",
    "            \"red cube on the left near the blue cylinder\",\n",
    "            \"pick the red cube and place it at the goal\",\n",
    "            \"inspect the blue object before moving\",\n",
    "        ))\n",
    "\n",
    "    def reset(self):\n",
    "        self._i = 0\n",
    "\n",
    "    def step(self, _):\n",
    "        text = self.captions[self._i % len(self.captions)]\n",
    "        self._i += 1\n",
    "        return Caption(text=text, source=\"golden.language_examples\")\n",
    "\n",
    "\n",
    "class CaptionPlanner(Flow[Caption, PlanText]):\n",
    "    \"\"\"Caption -> ordered primitive steps. Rules here; swap for an LLM later.\"\"\"\n",
    "\n",
    "    def step(self, caption: Caption) -> PlanText:\n",
    "        text = caption.text.lower()\n",
    "        steps = [PlanStepText(index=0, text=f\"inspect scene: {caption.text}\", action_label=\"inspect\")]\n",
    "        if \"red\" in text:\n",
    "            steps.append(PlanStepText(index=len(steps), text=\"focus on the red object\", action_label=\"focus\"))\n",
    "        if \"pick\" in text or \"grasp\" in text:\n",
    "            steps.append(PlanStepText(index=len(steps), text=\"pick the target object\", action_label=\"pick\"))\n",
    "        if \"place\" in text or \"goal\" in text:\n",
    "            steps.append(PlanStepText(index=len(steps), text=\"place the target at the goal\", action_label=\"place\"))\n",
    "        return PlanText(steps=tuple(steps), summary=caption.text, source=\"caption_planner\")\n",
    "\n",
    "\n",
    "class PlanTextPrinter(Flow[PlanText, None]):\n",
    "    def step(self, plan: PlanText) -> None:\n",
    "        print(\"plan=\" + str([f\"{s.index}:{s.action_label}:{s.text}\" for s in plan.steps]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d91aa4e",
   "metadata": {},
   "source": [
    "`CaptionPlanner` only reads `caption.text` and returns a typed `PlanText`. That\n",
    "isolation is the whole point: replace the rule body with a real model and the\n",
    "graph is unchanged."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3194f69",
   "metadata": {},
   "source": [
    "## Wire it and run\n",
    "\n",
    "Clocks say *when* each Flow runs; `sync=` says *which* input record it consumes.\n",
    "The source ticks on a `Rate`; the planner and printer are `Trigger`ed by new\n",
    "arrivals on the port named in the type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e431ffc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from retriever.flow import Latest, Pipeline, Rate, Trigger\n",
    "\n",
    "pipe = Pipeline(\"language_caption_plan\")\n",
    "with pipe:\n",
    "    source = CaptionSource() @ Rate(hz=10)\n",
    "    planner = CaptionPlanner() @ Trigger(\"text\")     # wakes on a new Caption.text\n",
    "    printer = PlanTextPrinter() @ Trigger(\"summary\")  # wakes on a new PlanText.summary\n",
    "    pipe.connect(source, planner, sync=Latest())\n",
    "    pipe.connect(planner, printer, sync=Latest())\n",
    "\n",
    "for _ in range(3):\n",
    "    pipe.step(dt=0.1)  # in-process; set a breakpoint in any step()\n",
    "pipe.close_stepper()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e807d43",
   "metadata": {},
   "source": [
    "## Why it's shaped this way\n",
    "\n",
    "`Caption`, `PlanText`, and `PlanStepText` are **standard payloads** — the same\n",
    "types perception, memory, and planning Flows hand each other. Because the\n",
    "contract is typed and explicit, you can swap `CaptionPlanner`'s rule body for a\n",
    "real VLM without touching the graph, step the pipeline in-process and\n",
    "breakpoint inside any `step()`, and record then replay the exact stream later."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "notebook_metadata_filter": "kernelspec,language_info"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
