u2v-lp-tasks¶
✍️ Writer · dispatcher-routed · writes to Attio
What it does¶
u2v-lp-tasks is the smallest writer, and a clean illustration of the whole philosophy. A teammate
emails a one-line request — "remind Philipp and me to follow up with Precitec by Friday" — and the
automation creates exactly one follow-up task on that existing LP in Attio, with all the named
people as assignees. If anything is unclear, it asks rather than guessing. It never invents a task,
and it never creates a new LP.
The workflow¶
flowchart TD
A["Email: 'follow up with Precitec by Friday, assign Philipp + me'"] --> B["Extract (1 Haiku call):<br/>LP · assignees · due-date phrase · description"]
B --> C{{"Gate 1: find the LP in Attio"}}
C -->|0 or many matches| Cx["Reply asking to clarify"]
C -->|exactly 1| D{{"Gate 2: resolve every assignee"}}
D -->|any unknown| Dx["Reply naming who couldn't be resolved"]
D --> E{{"Gate 3: work out the due date"}}
E -->|unclear| Ex["Reply asking for the date"]
E --> F["Create ONE task<br/>(all assignees in one array)"]
The router is run_task in pipeline.py:
three ordered gates, and a write only if all three pass.
Key decisions¶
Never guess — three gates, each with a clarifying reply. The LP must resolve to exactly one company (zero or many → ask), every assignee must resolve to a known teammate (any miss → say who couldn't be resolved), and the due date must be unambiguous (unclear → ask). Only then does it write. Notably it does not fall back to the sender as the assignee — defaulting would be a guess.
The model classifies; the code decides. The single Haiku call does one thing: pull the LP
reference, the assignee names, the due-date phrase, and the description out of the email — verbatim,
resolving nothing. All the consequential work is deterministic: matching the LP and the people, and
especially the due-date math in dates.py,
which turns "Friday" or "in three days" into an actual date in pure Python (with the reference date
passed in, so it's trivially testable). Turning "Friday" into a date is arithmetic — the wrong job for
a language model.
One task, many assignees — not a fan-out. Attio supports multiple assignees on a single task, so the automation creates one task with all the workspace members in a single array. No loop, no duplicate tasks — it mirrors Attio's own model.
Read the LP, never create it. The LP lookup is strictly read-only; there is no code path that would create a company. This automation only ever adds a task to something that already exists.
Least privilege — its own keys, exactly like lp-intake: its own scoped Attio and
Anthropic credentials, with its own .env overriding any broader inherited key.
Where things live¶
| Path | Role |
|---|---|
src/u2v_lp_tasks/pipeline.py |
run_task — the three-gate router |
src/u2v_lp_tasks/extract.py |
The one Haiku call → a structured request |
src/u2v_lp_tasks/dates.py |
Deterministic due-date math (no model, no wall-clock inside) |
src/u2v_lp_tasks/owners.py |
Assignee resolution — matches known teammates only |
src/u2v_lp_tasks/attio.py |
Read-only LP lookup + the single task write |
src/u2v_lp_tasks/reply.py |
Deterministic reply/clarifying-question text |
stages/extract_task.md |
The extraction rubric ("empty beats wrong; never compute a date") |
reference/owners.yaml |
The team roster (name → email + workspace-member id) |
reference/attio_field_map.yaml |
The task write map + LP lookup map |
config.yaml |
The extraction model + body length limit |
Knobs a non-engineer can change: the extraction model in config.yaml, and the team roster in
reference/owners.yaml (who can be assigned).
Status & how to run it¶
The task write shape is confirmed live (a disposable smoke test created a task with two assignees + a linked company, read it back, and deleted it), and the pipeline is offline-tested (58 tests, covering every gate and every no-write path). One step remains: a real end-to-end email through the dispatcher.
cd u2v-lp-tasks
PYTHONPATH=src .venv/bin/python -m u2v_lp_tasks.cli preflight
PYTHONPATH=src .venv/bin/python -m u2v_lp_tasks.cli create-task --owner benedikt@u2v.vc --body-file email.txt --json
Go deeper
The due-date parser handles a surprising range of phrasings. Open the repo in
Claude Code and ask "show me every due-date form dates.py
understands and how the priority order works."