Twenty Engine Sync
The shared read/write capability every Twenty-touching outbound-engine skill depends on. Resolve identity, keep one golden record per person, and write additively. Reads are free; every write is dry-run first and operator-gated.
Twenty is the sales system of record and is shared with live SDR work. Never delete, never overwrite a populated identity value, never send outreach, never spend enrichment credits, never change schema/metadata. Ambiguity halts.
Capabilities
- Query by identity — look up a Person by
linkedinLink.primaryLinkUrloremails.primaryEmail, plus ICP filters, withfind_many_*+ explicitselect. - Identity resolution (RL-439) — classify a candidate as
match,no-match,fuzzy-candidate, orcollision. Seereferences/identity-resolution.md. - Idempotent match-or-create — 0 matches →
would-create; 1 →would-update(additive, by id); ≥2 →collision-skip(halt). Noupsert_*on email/LinkedIn (no unique constraint). - Auto-merge + survivorship (RL-440) — fold matches into one golden record
by field class (identity keys fill-if-empty; enrichment freshest-wins; manual
override wins; multi-value appends). See
references/merge-survivorship.md. - Fuzzy → human-QA queue (RL-441) — surface name+company candidates and all
collisions to a review queue; never auto-merge fuzzy. See
references/fuzzy-qa-queue.md. - Suppression + routing (defer to
eligibility-gate) — the per-person verdict (suppress / skip / source / re-enrich / reuse) and routing are owned by theeligibility-gateskill, the single deterministic decision authority. Run the gate first; this skill owns only the writes that carry out a cleared verdict (membership enroll viaactiveCampaignMembershipId+status=ACTIVE, the newcampaignTouch, and binding the routedsendingAccount).
Golden-record schema
references/golden-record-schema.md holds the object/field/identity map
(Person, Campaign, CampaignMembership, campaignTouch, SendingAccount, EngineConfig).
Identity precedence is always linkedinLink.primaryLinkUrl → emails.primaryEmail.
Operating loop
- Connect and learn exact tool schemas (meta-tool pattern:
get_tool_catalog→learn_tools→execute_tool); never guess operation schemas. - Resolve identity for each candidate with the decision layer in
demo/engine_identity.py. - Run
eligibility-gateper record for the verdict (suppress / skip / source / re-enrich / reuse) + routing; do not re-derive suppression, TTL, or routing here. - Produce a dry plan: per record
would-create/would-update/collision-skip/blocked, with field-level diffs and survivorship audit. - Route fuzzy + collisions to the QA queue. Do not auto-merge them.
- Ask for approval before any create, update, merge, action, or external effect.
- Execute only the approved operation, additive and by id.
- Read back and verify the changed record and the guarded non-changes.
Reference implementation + demo
demo/engine_identity.py is the dependency-free decision layer (normalization,
Jaro-Winkler + token-set fuzzy scoring, resolver, survivorship merge planner,
queue builder). It performs no Twenty access. demo/run_demo.py exercises it on
the synthetic sample in demo/sample_data.py:
python3 demo/run_demo.py
Expected: 2 match, 1 no-match, 1 fuzzy-candidate, 2 collision; two dry
merge plans; a 3-item QA queue; Twenty writes performed: 0.
Guarantees
Additive, idempotent, non-destructive. Reads free. Forbidden without explicit approval: deletes, overwriting populated fields, schema/metadata changes, outreach send, credit spend, Paperclip skill install/attach. Halt on identity collision or schema drift. Read-back verification after every write.
Callers
Operator pipeline: the local outreach-* stage runners (outreach-source,
outreach-resolve, outreach-enrich, outreach-gate, outreach-assemble,
outreach-review) route their Twenty reads and writes through this skill.
Paperclip engine tasks: RL-429 (Apollo ingest), RL-430 (Clay write-back), RL-431 (campaign assembly), RL-433 (outcome write-back), RL-439/440/441 (identity + merge wave).