# Autotouch CLI Agent Playbook

Use this guide when an agent should operate the platform with minimal hand-holding.

The goal is:

- discover valid setup from `autotouch capabilities`
- compose workflows from machine-readable contracts
- run controlled slices
- audit exact row/cell outcomes instead of guessing from terminal output

For the full command surface, use [the CLI reference](../reference/autotouch-cli.md). For the thin package/PyPI entry point, use [the repo README](../../../README.md).

## Product Model

Autotouch helps GTM teams source records, land them in Smart Table, research and operate on those rows, then turn qualified accounts/leads into outreach actions.

The lifecycle is:

1. **Source Records**: list builds, searches, recurring scheduled work, imports, and webhooks create or receive companies, people, or signal events.
2. **Land In Smart Table**: source records become Smart Table rows with provenance, identity, dedupe, and job status.
3. **Research And Operate**: Smart Table columns enrich, score, split JSON, discover people, find emails/phones, and track progress/spend.
4. **Engage**: qualified rows move to Autotouch Leads, Sequences, Tasks, CRM, or outreach providers.

Smart Table is one durable operating layer. A request may keep results in a Search artifact, a List Builder job, a CRM workspace, or a Research Table; the requested outcome and the selected operation determine the destination and follow-up work.

Use `autotouch workflows list --output json` to inspect lifecycle and workflow-plan reference metadata. Workflow plan IDs are canonical snake_case values only; do not translate them to dash-case aliases. Use `autotouch capabilities --output json --select product_lifecycle` when you only need the product model.

### List Builder column visibility

New people and company lists show the core identity, company, and location fields. Additional writable provider fields retain their values in hidden columns, available through **Columns**. Similar-company lists also show search rank and similarity score. First and last names remain separate fields for personalization and exports.

Omit `destination.field_mapping` for these defaults. Use `destination.presentation.include_fields` to show supported optional fields at creation, and explicit field mappings for custom destinations. Append and resume preserve existing column visibility. Hidden optional fields remain ordinary writable fields; identity and provenance fields retain their separate protections. Automatic lookup details and component scores stay hidden while the primary fit score remains visible.

Provider `has_email`, `has_phone`, mobile-availability aliases, and contactability/count hints are excluded from list columns and public result items. They do not describe AutoTouch's multi-provider contact-finding capability. Actual Company Phone values remain distinct from a person's phone or mobile number.

## Product AI vs CLI Agents

The CLI is the external/local agent contract. It is the right surface for Codex-style local operators, customer scripts, and API-key workflows outside the product.

The in-product AI system should not shell out to `autotouch` from the web/API runtime. Product assistants such as Command Center should use the same contracts the CLI exposes, then call typed backend services or API endpoints directly:

- Plan from `GET /api/capabilities`, canonical workflow plans, provider setup contracts, and the integration catalog.
- Use `autotouch cli-manifest --output json` and `autotouch cli-reference` as local command grammar for external agents, not as a server-internal dependency.
- Keep preview and mutation separate. Preview is read-only; run/execute requires explicit user confirmation and uses the service endpoint that owns that domain.
- Render tool progress from backend-reported execution events or action journals so the UI can show a build-log style stream without guessing.
- When command semantics change, update the API/capabilities contract and regenerated CLI artifacts together.

## Codex-Style Operating Loop

Use the CLI as a tool surface, not as a chat transcript. A good agent turn should resemble the Codex loop:

```text
orient -> inspect tools/contracts -> make a small plan -> run one bounded action -> observe authoritative state -> continue
```

### 1. Orient

Identify the user's goal, the workspace/auth context, and the concrete artifact you are operating on. Prefer explicit IDs from prior command output:

- `TABLE_ID`
- `ROW_ID`
- `COLUMN_ID`
- `OPERATION_RUN_ID`
- `JOB_ID`
- `LEAD_ID`
- `SEQUENCE_ID`

If the user has not identified the object, inspect and present options. Do not silently choose a table, sequence, sender, provider, or destructive target because it was recently used.

### 2. Discovery / Inspect

Discovery is a phase that uses existing inspection commands, not a separate top-level `autotouch discover` command. Start from machine-readable surfaces:

```bash
autotouch auth check --output json
autotouch capabilities --output json
autotouch capabilities operations list --cli-available --output json
autotouch workflows list --output json
autotouch cli-manifest --output json
```

Use the focused catalog as needed: `autotouch capabilities operations get <operation_id>` for product-operation bindings, `autotouch workflows plan --type <TYPE>` for reference lifecycle context, `autotouch integrations list/status` for sources and destinations, `autotouch list-build inputs` for operation-specific filter values, and the scheduled-work catalog for recurring-work configuration.

Then inspect the narrow object you need:

```bash
autotouch tables list --output json
autotouch rows get --table-id "$TABLE_ID" --row-id "$ROW_ID" --output json
autotouch cells get --row-id "$ROW_ID" --column-id "$COLUMN_ID" --output json
# Bulk-job inspection is compatibility/debug only for Research Table runs.
# `columns run --wait` observes the canonical operation-run receipt.
autotouch jobs get --job-id "$JOB_ID" --output json
```

### 3. Plan

Choose the operation whose contract matches the user's requested outcome and the
current evidence. When a table is part of that outcome, a common lifecycle is:

- source records may remain in a Search artifact, List Builder job, CRM
  workspace, or another supported result store
- a selected import or materialization operation can land them in Smart Table
- table operations can enrich, research, qualify, or reshape the rows
- destination operations can sync Leads, Sequences, Tasks, CRM, or providers
  when that is the requested outcome

These are supported representations, not a required order. After every
observation, include the authoritative result in the conversation and let the
model decide whether to answer, ask, inspect, or invoke another valid operation.

Ask the user only for high-level missing decisions. Do not ask them for implementation details the CLI can infer from contracts, recipes, or schema inspection.

### 4. Preview Or Scaffold

For JSON-heavy work, generate a payload file first:

```bash
autotouch workflows plan --type research_to_sequence --output json
autotouch workflows scaffold --type research_to_sequence --out-dir workflow
autotouch columns recipe --type <TYPE> --out-file column.json
```

Review the workflow plan, local scaffold files, payload file, table ID, run scope, and downstream side effects before executing. Plan/recipe output is read-only; scaffold writes local files only; create/run/enroll/send commands are product mutations.

### 5. Execute A Bounded Slice

Use controlled runs while validating:

```bash
autotouch columns run \
  --table-id "$TABLE_ID" \
  --column-id "$COLUMN_ID" \
  --scope firstN \
  --first-n 10 \
  --unprocessed-only \
  --wait \
  --output json
```

For sourcing/list-build tests, use small targets unless the user explicitly asks for a larger run. For table workflows, prefer `run-next` or `firstN + --unprocessed-only` before expanding to all rows.

### 6. Observe

Treat backend state as authoritative:

- the `operation_run` receipt returned by `columns run --wait` for canonical
  terminal status and progress
- `jobs get` only when you are inspecting the native executor bulk job
- `rows get` for row-level state
- `cells get` for exact value/status/error metadata
- domain commands such as `leads query`, `sequences get`, or `sequences stats` when the workflow writes outside the table

Do not infer success from local terminal text, a toast, or a progress card alone.

### 7. Continue

Carry forward exact IDs when a later operation needs them. Include the
authoritative observation in the conversation, then reassess the original
request: the model may answer, ask for a product-level decision, inspect more
evidence, or choose another valid operation. A prior recommendation does not
make a later operation mandatory.

Good:

```text
I created column col_123 on table tbl_123 and job job_123 processed 10/10 rows.
The run receipt and row/cell observations are available for deciding whether
the requested outcome is complete or needs another operation.
```

Bad:

```text
What would you like to do?
```

### Safety Rules

- No sends, sequence activation, deletes, provider campaign writes, or global context changes without explicit user intent and a mutation command.
- No hidden active table/sequence. Use explicit IDs or present choices.
- No duplicate workflow logic in shell scripts when a typed CLI/API workflow exists.
- Errors are evidence. Inspect raw JSON and backend job state before retrying with a different command.
- Use `/private/tmp/openai-codex` only as local Codex reference inspiration for operating-model patterns. Do not depend on it from CLI code or docs generated into the package.

External/local agent install from the configured package index:

```bash
pipx install autotouch-cli
# or
python -m pip install autotouch-cli

# upgrade
pipx upgrade autotouch-cli
# or
python -m pip install --upgrade autotouch-cli
```

Before a release tag publishes, install the current repo branch directly:

```bash
pipx install --force "git+ssh://git@github.com/nicolonic/autotouch_main.git@<branch-or-sha>"
# or
python -m pip install -U "git+ssh://git@github.com/nicolonic/autotouch_main.git@<branch-or-sha>"
```

Confirm the agent is using the expected binary and bundled parser docs:

```bash
autotouch --version
autotouch cli-reference --source bundled | grep -n "valueSchema"
```

## Auth Decision Rule

1. If the user provided an API key, run `autotouch auth set-key ...` and then `autotouch auth check`.
2. If existing auth passes `autotouch auth check`, use it.
3. If `autotouch auth check` fails in a collaborative workflow, ask for an existing API key/account.
4. For a fresh account, register and sign in through the web app, then create a developer key from the authenticated workspace.
5. If the first auth-bearing command says `missing token`, stop and resolve one of those authenticated paths before continuing.

The former public `autotouch auth bootstrap` flow is retired and returns
`410 Gone`.

## First Three Commands

```bash
autotouch auth check --output json
autotouch capabilities --output json
autotouch columns recipe --type <TYPE> --out-file payload.json
```

Important recipe rule:

- `autotouch columns recipe --out-file payload.json` writes the actual create payload at the JSON root.
- Do not unwrap it through `.payload` before passing it to `autotouch columns create`.

## Capacity Preflight

Before creating a large list, enrolling many leads, or adding more outreach tasks, inspect channel capacity:

```bash
autotouch capacity overview --scope org --output json
autotouch capacity channels --scope org --output json
autotouch capacity recommend --scope org --output json
```

Use `--scope me` when the question is about the current sender/rep only. If the CLI is using an org-level developer key, target a specific rep with `--scope me --user-id <user_id>` or `--scope me --user-email <email>`. The capacity commands are read-only wrappers around Command Center's backend-owned capacity model, require the `tasks:read` developer-key scope, and should be used for planning and prioritization, not as a substitute for sequence/task/list-build execution commands.

## Capability Sections That Matter

Read these keys first from `autotouch capabilities --output json`:

- `automation.auto_run`
  - valid auto-run policies and conditional filter field names
- `automation.providers`
  - provider-specific setup contract, dispatch strategy, composition contract, and `save_time_backfill` metadata; trust `save_time_backfill.supported` for whether create/update can backfill existing rows
- `workflow_plans`
  - workflow-plan reference metadata, lifecycle paths, and known input questions
- `inspection.research_table`
  - row/cell inspection endpoints plus the recommended audit loop
- `filtering`
  - valid run scopes and filter semantics

For contact enrichment, inspect the executable operation details before writing
recipes:

```bash
autotouch capabilities operations get research_table.formatter --output json
autotouch capabilities operations get research_table.email_finder --output json
```

Use `research_table.formatter` when a scalar name/full-name/person-name column
needs to become `first_name` and `last_name`. Use
`research_table.split_json_fields` only when those identity values already
exist inside JSON/object cells. For email finder, prefer the operation detail's
`auto` mapping path when LinkedIn URL plus name and domain/company fallbacks are
available.

## CRM Custom Field Rule

Custom Lead, Company, and Deal fields are organization-scoped CRM fields, not research
table columns. Before mapping or writing one, discover the active definition
key from the field catalog:

```bash
autotouch capabilities operations get crm.field_catalog --output json
autotouch crm fields list --entity lead --custom-only --output json
autotouch crm fields list --entity company --custom-only --output json
autotouch crm fields list --entity deal --custom-only --output json
```

Use the field `key` for writes and mappings. Do not use the display label,
definition id, Mongo path, or a guessed snake_case value.

```bash
autotouch crm fields create \
  --entity lead \
  --label "Priority" \
  --type select \
  --option high=High \
  --option low=Low \
  --output json

autotouch leads custom-fields set \
  --lead-id <LEAD_ID> \
  --field-key priority \
  --value high \
  --output json

autotouch companies custom-fields bulk-set \
  --company-id <COMPANY_ID_1> \
  --company-id <COMPANY_ID_2> \
  --field-key account_tier \
  --value strategic \
  --output json
```

Deal custom-field values are written through `autotouch deals create` or
`autotouch deals update` using a `custom_fields` object keyed by the definition
key. Deal fields do not run AI autofill or Sync to Leads.

For Sync to Leads, put custom field mappings in the `leadCustomFields[]` and
`companyCustomFields[]` arrays from the recipe payload. For direct CRM value
writes after a Lead/Company already exists, use the dedicated `custom-fields`
commands above instead of generic `leads update` or `companies update`.

## Workflow Capability Facts

The current operation catalog includes agent `llm_enrichment` for company-table
people discovery when:

- the source records are already company/account rows in a Smart Table or a prior
  company preview
- the user asks to find people, contacts, buyers, decision makers, or prospects
  at those companies
- the right buyer is semantic, for example "find persons most responsible for
  partnerships", "find people who own lifecycle marketing", or "find buyers for
  usage-based billing"

Author this as an AI/agent column with explicit row-scoped instructions. For one
lead per company, return one JSON object with `first_name`, `last_name`, `title`,
`company_name`, `company_domain`, `linkedin_url`, and `reason`. Use
`researchMode: "lite"` when the target responsibility is clear and exactly one
person is needed.

The same operation family also supports:

- the workflow needs custom research output
- the target contacts are hard to find
  - the output should be a custom JSON shape; company-row people discovery can
    return a `contacts[]` array
  - the user asks for 1-3 recommended people; the operation supports
    `researchMode: "heavy"` and a top-level `contacts` array

The `autotouch linkedin ...` commands expose connected-account diagnostics and
LinkedIn task support:

- checking LinkedIn connection status
- checking connected-account limits or cooldowns
- supporting LinkedIn task execution workflows after a sequence/task already exists

List Builder operations source net-new company, lead, and job records. CRM query
operations read existing workspace records. The model selects between those
surfaces from the requested entity state and available evidence; the runtime
does not route by wording or page.

LinkedIn is one available campaign channel. Its connected-account, profile,
cooldown, and delivery contracts are distinct from email, calls, custom tasks,
research columns, and Scheduled Chat. The model selects a channel from the user's
requested outcome and current provider facts; no channel is selected merely by
page, keyword, or default preference.

## Sequence Campaign Intake Rule

Sequence and campaign requests have two valid modes:

- Execute mode: the user gave a specific campaign spec or explicitly asked to draft/scaffold exact assets. It is okay to use recipes, write payload files, create sequences/columns, and enroll leads according to that spec.
- Design mode: the user asks to "help build", "come up with ideas", "recommend", "draft a campaign", or otherwise leaves campaign taste/preferences open. Do read-only discovery if useful, then ask or present options before creating files or running mutating commands.

Before creating sequence payload files, live sequences, lead-sync columns, add-to-sequence columns, or enrollments, confirm these decisions unless they are already explicit:

- target audience / persona
- offer, product, or reason to reach out
- source table or lead set
- channel mix: email, LinkedIn, calls, custom tasks, external provider
- manual AI-draft review vs template automation vs trusted automation
- tone/taste constraints and examples
- assignee, sender identity, and team routing
- enrollment criteria: all rows, qualified only, filtered rows, sample first
- activation policy: draft/scaffold only, create live, enroll review-only, or activate/run now

Allowed before confirmation: `auth check`, `auth status`, `capabilities`, `workflows plan`, read-only list/status/schema commands, and summarizing candidate tables/sequences. Do not use `sequences recipe` to choose the campaign strategy; use it only after the campaign decisions are known.

## Sequence Ownership And Senders

Keep these concepts separate when adding leads to a sequence:

- `assignedToUserId` is the task owner/reviewer. This is who should see and manage the sequence tasks.
- `senderAccountIds.email` and `senderAccountIds.linkedin` are the actual sender identities for personal email and LinkedIn steps.
- If sender account IDs are omitted, personal email/LinkedIn execution uses the assignee's default connected sender accounts.
- If a manager should review work sent from another rep's mailbox, keep `assignedToUserId` as the manager and set explicit sender account IDs for the rep.
- For Instantly/Smartlead bulk email sequences, email sender pools live on the provider campaign/sequence and are selected with `autotouch sequences sync-accounts`; per-enrollment email sender IDs do not control bulk provider mailboxes.

Example: Zack manages/reviews tasks, but Ryan's Gmail and LinkedIn send the touches:

```bash
autotouch sender-accounts list --channel email --include-all --output json
autotouch sender-accounts list --channel linkedin --include-all --output json

autotouch sequences enroll \
  --sequence-id <SEQUENCE_ID> \
  --lead-ids-file leads.json \
  --assigned-to-user-id <ZACK_USER_ID> \
  --email-sender-account-id <RYAN_GMAIL_SENDER_ACCOUNT_ID> \
  --linkedin-sender-account-id <RYAN_LINKEDIN_SENDER_ACCOUNT_ID> \
  --skip-active-sequence-conflicts \
  --wait \
  --output json
```

Research-table `to_sequence` / `add_to_sequence` config for the same pattern:

```json
{
  "config": {
    "sequenceId": "<SEQUENCE_ID>",
    "sourceLeadColumn": "sync_to_leads",
    "assignedToUserId": "<ZACK_USER_ID>",
    "senderAccountIds": {
      "email": "<RYAN_GMAIL_SENDER_ACCOUNT_ID>",
      "linkedin": "<RYAN_LINKEDIN_SENDER_ACCOUNT_ID>"
    },
    "skipActiveSequenceConflicts": true
  }
}
```

Only use `--update-lead-owner` when the Lead owner should actually move to the assignee. It is not required just to assign sequence tasks or send from a specific account.

## Sequence Automation Choice Rule

Do not infer that "AI draft" means "automated send." Treat these as separate choices:

- Manual AI-draft review: use `autotouch sequences recipe --type manual_review` (or `create`) when the user wants personalization and is willing to approve tasks. This sets `executionMode=MANUAL` and `aiDraft=true`.
- Template-based automated send: use `autotouch sequences recipe --type automated_template` (or `bulk_automated`) when the user explicitly says they want to automate sending without review. This uses fixed templates with `executionMode=AUTOMATED` and `emailSendMode=AUTOMATED`.
- LinkedIn-assisted outreach: use `autotouch sequences recipe --type linkedin_outreach` only when the campaign should include LinkedIn touches. In the current recipe, profile visit is automated; connection request, connected-only LinkedIn chat, and fallback email are manual AI-draft steps by default. Use `LINKEDIN_MESSAGE` for best-available LinkedIn delivery when the path is not guaranteed to be connected; use `LINKEDIN_CHAT_MESSAGE` only after a connection-accepted branch.
- Research-field routing: use `autotouch sequences recipe --type research_field_routing` when one linked source table should drive multiple paths in one sequence based on a column value such as persona, segment, territory, product line, or campaign path.

If the user says "automate the sequence" and does not explicitly ask for automated AI-written sends, choose the template-based automated path. You can explain that manual AI-draft review is the higher-personalization path because drafts can improve with feedback, but it creates upfront manual tasks.

`autotouch sequences create` and `autotouch sequences update` return verified readback fields (`sequence`, `verified_readback`, `readback_shape`, `readback_shape_matches_submitted`, `verification_hint`) so the saved API shape can be summarized directly. They also return `sequence_authoring_warnings` when `aiDraft=true` is mixed with template fields. Treat those warnings as contract guidance: AI-draft templates are context, not guaranteed verbatim output; use `aiDraft=false` for deterministic variable-filled template sending.

## Clone And Edit A Sequence Safely

Clone is useful when the source already has the desired topology. It copies a
valid `flowGraph`, but it does not infer or add conditions and branches. Use the
same explicit production base URL on every command; saved config or environment
may point somewhere else, and an explicit `--base-url` removes that ambiguity.
The authenticated session or developer key must belong to the source sequence's
organization and a developer key needs both `sequences:read` and
`sequences:write` for this flow.

Run the production/auth and exact-source preflight before the first write:

```bash
BASE_URL=https://app.autotouch.ai
SOURCE_SEQUENCE_ID="<SOURCE_SEQUENCE_ID>"
TARGET_TABLE_ID="<TARGET_TABLE_ID>"
TARGET_TABLE_NAME="<TARGET_TABLE_NAME>"

autotouch auth check \
  --base-url "$BASE_URL" \
  --output json --compact

autotouch sequences get \
  --sequence-id "$SOURCE_SEQUENCE_ID" \
  --base-url "$BASE_URL" \
  --output json --compact
```

Then clone one DRAFT. Optional retargeting and naming are one request body, not
a follow-up metadata guess:

```bash
autotouch sequences clone \
  --sequence-id "$SOURCE_SEQUENCE_ID" \
  --name "Custom sequence clone" \
  --target-source-table-id "$TARGET_TABLE_ID" \
  --target-source-table-name "$TARGET_TABLE_NAME" \
  --base-url "$BASE_URL" \
  --output json > clone-result.json

CLONE_ID=$(jq -r '.sequence.id // .id // .sequenceId' clone-result.json)
test -n "$CLONE_ID" && test "$CLONE_ID" != null
```

Omit both target-table flags only when the source already has the intended
`sourceTableId`. Developer-key create/update calls require a non-empty
`sourceTableId`, so retarget a scratch or unlinked source during clone before
the later update. Never edit the source template in place. Read the new DRAFT,
project the full writable `SequenceIn` configuration, and edit that complete
document. `sequences update` is a full configuration replacement; sending only
changed steps can drop the existing graph or other configuration.

```bash
autotouch sequences get \
  --sequence-id "$CLONE_ID" \
  --base-url "$BASE_URL" \
  --output json > clone-readback.json

jq '{
  name, description, steps, exitRules, schedule,
  enrollmentRules, requiredVariables, emailDelivery, inboxAccess,
  senderAccountIds, senderBindings, defaultAppendSignature, allowHqFallback,
  aiAutomationTrusted, aiDraftSettings, sourceTableId, sourceTableName,
  variableMap, flowGraph, voiceStyle
} | with_entries(select(.value != null))' \
  clone-readback.json > sequence-update.json

# Edit sequence-update.json, including steps and flowGraph together.
autotouch sequences update \
  --sequence-id "$CLONE_ID" \
  --data-file sequence-update.json \
  --base-url "$BASE_URL" \
  --output json --compact
```

Treat the final update output as successful only when
`verified_readback=true`. Inspect `readback_shape.has_branching` and the saved
`sequence.flowGraph`, not just the submitted JSON.

For a new custom branch topology, begin with the typed recipe instead of
assuming clone will invent it:

```bash
autotouch sequences recipe \
  --type research_field_routing \
  --out-file sequence-update.json
```

Edit the recipe's cases, branch nodes, actions, and source-field contract as one
graph, then use `sequences create` for a separate DRAFT or merge the complete
validated `steps` plus `flowGraph` into the cloned DRAFT before running
`sequences update`.

Version guard:

- Check `autotouch --version` before relying on sequence defaults; branching
  recipes require `autotouch-cli` `0.2.121+`. Versions through `0.2.120` can
  emit legacy `next` / `condition` edge kinds that the canonical
  `SequenceFlowGraph` rejects.
- If an upgraded package is installed but `which autotouch` still resolves to an older shim, run the upgraded binary directly or fix PATH before creating production sequences.
- Always inspect the live payload first:

```bash
autotouch sequences recipe --type linkedin_outreach --output json --compact
```

Sequence variable guidance:

- Use `{{company_name}}` for normal human-facing company display copy.
- Use `{{company_name_clean}}` when outreach copy should strip legal suffixes like Inc, LLC, Ltd, GmbH, or S.A. while staying readable.
- Do not use `{{company_name_normalized}}` in outreach copy. It is an internal company match key for fallback lookup/debug use, not a display variable.

## Search, List-Build, And Scheduled-Work Facts

The bounded public Search family has thirteen typed operations:

- `search.local_businesses`
- `search.news`
- `search.business_reviews`
- `search.companies`
- `search.catalog_companies`
- `search.people`
- `search.people_basic`
- `search.similar_companies`
- `search.company_filings`
- `search.jobs`
- `search.linkedin_posts`
- `search.linkedin_post_engagers`
- `search.role_changes`

The public API and Command Center are adapters over the same registered Search
commands. The installed CLI currently exposes the original seven Search
operations; `search.jobs`, the two LinkedIn reads, `search.role_changes`,
`search.people_basic`, and `search.catalog_companies` are explicit CLI backlog
until real commands are added. In-product Command Center uses registered commands
directly after its typed
`providerSearchPlan` approval; it does not call the public REST endpoint and
does not bill a second time. A successful search response includes a compact
`result_ref`; the full normalized rows are retained in a short-lived,
actor-authorized result artifact rather than copied into agent context.

Bounded Search operations return immediate, reviewable candidate evidence. Their
public limits, source coverage, cost, provenance, and destination behavior live
in the operation contract. `autotouch search ...` is the external CLI adapter
when that operation has a real CLI binding; the in-product runtime uses the
same registered operation directly.

Neural search examples:

- "AI workflow platforms for healthcare"
- "AI workflow startups selling to law firms"
- "RevOps operators at PLG SaaS companies"

Neural search pricing and scale facts:

- Treat each `autotouch search companies`, `autotouch search people`, and `autotouch search similar-companies` command as one bounded neural API call.
- Neural company and similar-company search return up to 100 results per API call. Neural people search returns at most 10 results per API call.
- People neural search is responsibility-first: send CLI `--person-description` or API/data-file `person_description` phrased like "person responsible for ...". Use only optional company/location anchors on the public API. Internal research-agent tools may add a domain anchor when the live MCP schema exposes it. The CLI `--query` flag is only a deprecated alias; do not pass domain, title, skills, old `lead_description`, or plural criteria as public people-search inputs.
- Neural search costs 1 credit per API call.
- A neural Search call is not an implicit pre-step for a List Builder job. The
  model can select either lifecycle from the request and the available
  operation contracts.

Twelve public Search operations are provider-backed. Most are credit-bearing;
the two LinkedIn source reads instead require a connected LinkedIn account and
currently use no AutoTouch source credits. `search.catalog_companies` is a free
internal catalog read and does not require provider credentials. Inspect the
selected operation detail for the exact request, source, connection, and pricing
boundary before execution. Choose semantically from that metadata and ambient
context; no backend heuristic may auto-select, auto-switch, or silently merge
sources.
Search itself does not create a table, list-build job, or monitor.

List Builder operations create durable acquisition jobs with source-specific
filters, result limits or completion modes, continuation state, and optional
table destinations. The model selects this lifecycle when those facts match the
requested outcome; a table destination, result count, or keyword does not force
it.

Scheduled Chat provides recurring work. Their occurrence model may compose
the same bounded Search operations, code workspace, and Research Table
operations under a standing grant. Search, List Builder, and Scheduled are
separate supported lifecycles; the model chooses among them from the requested
outcome, evidence, cadence, and cost rather than a keyword or result-count
router.

Durable List Builder facts:

- Company and people commands use native `leadmagic_company_search` and `leadmagic_people_search` sources. All source-specific filters remain available through `--filters-json` or a complete `--data-file` request; inspect exact operation detail first.
- Person geography belongs in `people_filters`; employer filters belong in `company_filters`. Do not add department or seniority restrictions simply because a specific title was requested.
- Use `--from-job-id` with `--num-results` to acquire more from a completed delivered list. The backend restores the saved cursor, unchanged scope and table. Stored-result paging only reads rows already acquired.
- Use job search for posting recency and local-business search for places. Company/people results are provider evidence, not verification of current events or every requirement.
- Preserve user geography and role requirements; qualify broader candidates separately. Read runtime pricing rather than quoting fixed source charges.

LinkedIn campaign sequence rule:

- Use `autotouch sequences recipe --type linkedin_outreach` as the starting payload only when the sequence should include LinkedIn touches.
- In `0.2.115+`, the standard shape is automated profile visit -> manual AI-drafted connection request -> wait up to 7 days for connection acceptance -> connected branch to manual AI-drafted LinkedIn chat -> timeout branch to manual AI-drafted fallback email.
- The branch should wait for connection acceptance before creating the LinkedIn chat task.
- The not-connected path should route to an email fallback after the wait window.
- Do not hand-author branch IDs or condition metadata from memory; use the recipe/capabilities output and edit only visible copy, schedule, source table, and assignee/enrollment settings.
- Durable list builds run as background jobs with `status` and `results` commands.
- Read current list-build pricing and job receipts for source costs; enrichment has its own cost.

Research-field routing sequence rule:

- Use `autotouch sequences recipe --type research_field_routing` when the user wants one campaign whose route changes based on a source-table column value.
- Keep one route condition with `decisionType=source_field_route`, `source=source_table`, `columnKey`, `cases[]`, and `defaultBranchId`.
- Add one branch and one route case per expected value; for nine routes, keep one sequence and add nine branch cases unless schedules, senders, reporting, ownership, or activation need to be separate.
- Keep a default route for missing, misspelled, or unexpected values.
- Do not duplicate the sequence just to vary copy or steps by persona/segment; route inside the graph and put the branch-specific actions under each branch.

Recommended search/list split:

- `autotouch search companies` for niche semantic company discovery
- `autotouch search people` for niche semantic people/persona discovery when
  you can express the target as a `person_description`
- `autotouch list-build companies` for structured account lists at scale
- `autotouch list-build leads` for structured lead/contact lists, with employer criteria when relevant
- `autotouch list-build jobs` for structured job-posting lists and hiring signals
- `autotouch search similar-companies` to expand seed-account lists
- Command Center `find-similar-companies` when the user asks for a confirmed
  in-product account lookalike build that can later sync into Smart Table
- `autotouch search news`, `local-businesses`, `reviews`, and `filings` for intent and research layers

If the user later asks to combine search results and build a table, keep the
`result_ref` artifact ids, mount the authorized artifacts in the in-product
coding sandbox, and let the model author the semantic merge/cleanup there. The
final generated CSV should enter the standard Research Table import lifecycle
with explicit field mappings and identity/upsert policy. External CLI clients
that already have local JSON/CSV output may use the normal
`autotouch rows import-csv` flow; there is no separate deterministic
search-to-table transformer.

Example native list build (CLI 0.2.122+):

```bash
autotouch capabilities operations get lists.build_companies
autotouch list-build companies --country-code US --filters-json '{"max_employees":24}' --num-results 20 --table-name "Small companies" --wait
autotouch list-build leads --title "SDR Manager" --city "New York" --country-code US --num-results 20 --table-name "SDR managers" --wait
autotouch list-build leads --from-job-id lb_prior --num-results 20 --wait
```

`workflow_plans` is reference metadata for composition. Exact operation details,
typed schemas, current observations, and runtime boundaries are authoritative.

For website visitor or external-intent webhooks, the workflow layer exposes
reference plans and scaffolds for the available operation contracts:

```bash
autotouch workflows plan --type website_visitors --output json
autotouch workflows scaffold --type website_visitors --out-dir workflow
```

This workflow is the common pattern:

```text
provider webhook -> Smart Table -> Webhook Payload JSON -> JSON split columns -> enrich/find people/find email -> chosen destination
```

Separate starter tables are one supported representation when the provider emits
different company-level and person-level events:

- Company visitor events: identity is usually `company_domain` or `provider_company_id`; available follow-on operations include company enrichment, people discovery, email finding, and destination handoff.
- Person visitor events: identity is usually `email`, `linkedin_url`, or `provider_person_id`; available follow-on operations include JSON split, email find/verify when missing, and destination handoff.

## Table Webhook Ingest

Use table webhooks when an external system should push records into a Smart Table.

Setup flow:

1. Create or rotate the table token with `autotouch webhooks rotate --table-id <TABLE_ID>`.
2. Configure identity/upsert behavior with `autotouch webhooks configure-ingest`.
3. Test with `autotouch webhooks ingest`.
4. Split the raw `Webhook Payload` JSON into stable columns with `autotouch columns projections` when downstream mapping/filtering needs specific fields.
5. Give external providers the header-auth setup by default. Use the query-token URL only when the provider cannot send custom headers.

```bash
WEBHOOK_TOKEN=$(autotouch webhooks rotate --table-id "$TABLE_ID" --output json --select token)

autotouch webhooks configure-ingest \
  --table-id "$TABLE_ID" \
  --match-rule-id linkedin_url \
  --match-strategy linkedin_url \
  --duplicate-mode upsert

autotouch webhooks ingest \
  --table-id "$TABLE_ID" \
  --webhook-token "$WEBHOOK_TOKEN" \
  --records-json '{"records":[{"first_name":"Jane","last_name":"Doe","linkedin_url":"https://www.linkedin.com/in/jane-doe"}]}'
```

Provider auth rule:

- Recommended: send `X-Autotouch-Token: <WEBHOOK_TOKEN>`.
- No-header compatibility: use `https://app.autotouch.ai/api/webhooks/tables/<TABLE_ID>/ingest?token=<WEBHOOK_TOKEN>`.
- Use the compatibility URL only for providers that cannot send custom headers, for example tools that accept only a single webhook URL.
- Treat query-token URLs as sensitive because they may appear in provider logs or history; rotate the table token if exposed.
- For ingest-time upsert, the identity field must be present as a mapped flat field on the incoming row. If the provider sends only nested JSON, ingest a sample, split the payload, then configure the provider/payload mapping to send a flat identity field such as `company_domain`, `email`, or `linkedin_url`.

## Paced HTTP Request Columns

Use HTTP Request pacing when a column calls an external API or session-backed endpoint that may rate-limit or block parallel calls.

Start from the recipe and enable `config.rateLimit`:

```json
{
  "provider": "http_request",
  "method": "GET",
  "url": "https://api.example.com/company?domain={{domain}}",
  "headers": {
    "Authorization": "Bearer {{secrets.example_api_key}}"
  },
  "timeoutSeconds": 30,
  "rateLimit": {
    "enabled": true,
    "minDelayMs": 5000,
    "jitterMs": 1000,
    "maxConcurrency": 1,
    "scope": "credential"
  }
}
```

Rules:

- `maxConcurrency` currently supports only `1`; this is strict serialized pacing, not a general concurrent token bucket.
- `scope: "credential"` shares the throttle by org, resolved host, and referenced `{{secrets.name}}` names. If no secrets are referenced, it falls back to the column id.
- Pacing applies to background column runs. `columns test-http-request` is a preview and does not exercise the worker throttle.
- Prefer `scope: "credential"` for API keys, cookies, and browser-session headers; use `host` only when all calls to the host should share the same pace.

## Scheduled Chat

Scheduled Chat runs recurring Command Center tasks. Each occurrence creates a
fresh task, resolves the owner's exact resource references, and executes under
reviewed standing authority. The CLI uses `/api/scheduled` and requires the
owner's user session. Sign in with `autotouch auth login`; developer API keys
cannot invoke these routes.

Start with an explicit draft body, for example `schedule.json`:

```json
{
  "name": "Weekly account review",
  "prompt": "Review the selected account table and summarize material changes.",
  "recurrence": {
    "rrule": "FREQ=WEEKLY;BYDAY=MO;BYHOUR=9;BYMINUTE=0",
    "timezone": "America/New_York"
  },
  "resourceRefs": [{"type": "research_table", "id": "<TABLE_ID>"}]
}
```

```bash
autotouch scheduled create --data-file schedule.json
autotouch scheduled get <SCHEDULE_ID>
autotouch scheduled activate <SCHEDULE_ID> --expected-version <VERSION>
autotouch scheduled list --status active
autotouch scheduled run-now <SCHEDULE_ID> --expected-version <VERSION> --idempotency-key <STABLE_KEY>
autotouch scheduled runs <SCHEDULE_ID>
autotouch scheduled get-run <SCHEDULE_ID> <OCCURRENCE_ID>
autotouch scheduled cancel-run <SCHEDULE_ID> <OCCURRENCE_ID> --reason "No longer needed"
autotouch scheduled attention --unread-only
autotouch scheduled mark-attention-read <ATTENTION_ID>
```

Create returns a draft. Inspect its authority and resource references before
activation. `standingApprovalGrantId` is optional at create time and refers to a
host-issued grant; supplying an ID does not bypass the host's authority checks.
`expectedVersion` is the current returned version and protects edits and state
transitions from stale writes. `scheduled update` accepts a JSON object with
`expectedVersion` plus `name`, `prompt`, `recurrence`, or `resourceRefs`.
Refresh the version after every definition mutation. `scheduled pause` and
`scheduled archive` also require `--expected-version`.

`run-now` requires a stable `idempotencyKey` of 8-200 characters. Reuse it only
when retrying that same request. A returned occurrence is admission evidence;
follow its `operationRunId` and inspect the eventual task, turn, and exact
output resources before claiming completion. `cancel-run` requests
cancellation and returns the occurrence state; inspect it again to confirm a
terminal outcome.

## Other HTTP-backed product operations

`organization users` reads the current organization's active user catalog with
optional `--query`, `--limit` (1-1000), and `--offset`; developer keys require
`users:read`. `sequences table-readiness --sequence-id <ID>` reads canonical
Sync to Leads results for the linked table. Pass `--source-lead-column <KEY>`
when selecting an exact result column; this read never enrolls Leads or
activates the sequence. Developer keys require `sequences:read`, `tables:read`,
and `leads:read` together.

Use bounded search for an explicit small result set. `search board-jobs`
accepts an authored `--query`, a selected `--board indeed|google|linkedin`,
optional location/country, and at most 25 results. A full JSON body preserves
board-native freshness, sorting, job-type, and opaque pagination fields.
`search catalog-companies`
accepts deterministic catalog filters and returns at most 25 rows;
`search people-basic --query <KEYWORDS>` returns at most 10 profiles;
`search linkedin-posts` takes one query or up to five `--company-page` values
and returns at most 12 posts. `search linkedin-post-engagers --data-file
posts.json` takes up to 12 exact post references and at most 25 engagers per
type. `search role-changes --position cto --country US` unlocks at most five
records. Full bodies are accepted through `--data-json`/`--data-file`; provider
limits, credits, and authorization are enforced server-side. LinkedIn post
searches require both `search:run` and `linkedin:read`.

`calendar connections` and `calendar agenda` discover the caller's Calendar
state when the deployment enables Calendar. Agenda reads require explicit
timezone-aware `--start` and `--end` values within a 90-day range. Preserve the
returned cursor for the next page; `--page-size`, `--projection`, text and
attendee filters apply to the provider's paging contract. Use
`calendar get-agenda-event --provider google --event-id <ID>` for one exact
agenda event. `calendar events`, `get-event`, `update-event`, `delete-event`,
and `send-lead-invite` use Lead-associated event semantics and the caller's
connected provider. Optional `--expected-etag` protects update/delete from
stale writes. Invites need explicit Lead IDs, a UUID `request_id`, and future
timezone-aware start/end values. Calendar reads/writes require `calendar:read`
or `calendar:write` when using developer keys; scopes do not bypass ownership.

`companies promote --company-id <ID>` promotes the exact Company memory
identity. `companies refresh-firmographics --company-id <ID>` returns the
canonical `operation_run_id`; optional `--idempotency-key` supplies the native
refresh occurrence key. `companies refresh-firmographics-status` reads the
returned native `job_id` for provider diagnostics. All three require a user
session under the current HTTP policy.

For turnkey Enrich & Score, call `tables enrich-and-score-preflight` with the
exact `targetMode`, `scope`, `mappings`, and `options`. Review the returned
plan, then pass the same inputs plus its `planId`/`planVersion` and a stable
`idempotencyKey` to `tables enrich-and-score`. The backend rejects a stale or
changed plan. `tables enrich-and-score-retry` takes the exact terminal parent
operation ID and retries unfinished rows. Admission receipts are not completion
proof; use their canonical operation-run records and inspect persisted rows.

## Recommended Audit Loop

After every mutating workflow step:

1. Queue or wait for the run.
2. Confirm terminal backend state from the canonical `operation_run` receipt
   returned by `columns run --wait` (the native `job_id` is executor/debug
   metadata).
3. Inspect the exact row that should have changed with `autotouch rows get`.
4. Inspect the exact cell when value/status/metadata matters with `autotouch cells get`.
5. If the workflow writes outside the table, inspect the destination system directly:
   - `autotouch leads query`
   - `autotouch sequences get`
   - `autotouch sequences stats`

Canonical loop:

```bash
autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope firstN --first-n 10 --unprocessed-only --wait --output json
# Optional executor-level diagnostic after the operation receipt is terminal:
autotouch jobs get --job-id <JOB_ID> --output json
autotouch rows get --table-id <TABLE_ID> --row-id <ROW_ID> --output json
autotouch cells get --row-id <ROW_ID> --column-id <COLUMN_ID> --output json
```

## Two Core Patterns

### Workflow Plans

Use workflow plans for user goals, and recipes for individual payloads:

```text
discover -> plan -> scaffold/write explicit payloads -> run primitives -> inspect
```

Recommended entrypoints:

```bash
autotouch integrations list --output json
autotouch workflows list --output json
autotouch workflows plan --type bulk_outreach --output json
autotouch workflows scaffold --type research_to_sequence --out-dir workflow
```

Goal-level workflow types:

The workflow IDs below are the canonical values. Use them exactly as shown.

| Workflow | Lifecycle path | Use when |
| --- | --- | --- |
| `bulk_outreach` | Source Records -> Engage | Build/enrich a people list and push it to Instantly or Smartlead |
| `list_build_to_table` | Source Records -> Land In Smart Table | Source a new list before research/enrichment |
| `system_to_table` | Land In Smart Table -> Research And Operate | Land existing Autotouch Leads/Companies or workspace records in a Smart Table |
| `company_rows_to_people_table` | Research And Operate | Start from company/account rows and use AI research to find people |
| `table_to_outreach` | Research And Operate -> Engage | Push existing people/email rows to an external outreach campaign |
| `research_to_sequence` | Research And Operate -> Engage | Sync researched rows to Autotouch Leads and enroll them in a Sequence |
| `account_research` | Research And Operate -> Engage | Research accounts for rep enablement before manual AI-draft outreach |
| `signal_outreach` | Source Records -> Engage | Capture external signal feeds or webhooks and route qualified results |
| `website_visitors` | Source Records -> Engage | Ingest visitor or intent events, normalize them in Smart Table, then enrich and route |

Rules:

- workflow plans describe the path; they do not hide side effects
- scaffolds write local payload files plus ordered commands; review and edit them before running explicit primitives
- recipes remain leaf-level templates for one column, sequence, integration, lead, or task payload
- AI-draft sequence steps should default to manual review until the user explicitly trusts automation
- use `run-next`, `--show-estimate`, `--wait`, `rows get`, and `cells get` for
  execution and inspection; use `jobs get` only for native executor diagnostics

### Sources And Destinations

Use `autotouch integrations list` before choosing a data-movement workflow. Match the command to the direction the user asked for:

| User intent | CLI path |
| --- | --- |
| Pull records from HubSpot, Attio, or Salesforce into a research table | `autotouch integrations from-crm --provider hubspot --list-id <LIST_ID> --selected-field name --selected-field domain` |
| Push research-table rows to HubSpot or Attio | `autotouch columns recipe --type to_crm` |
| Create/configure an Instantly or Smartlead campaign | `autotouch outreach campaigns create --provider instantly --name "<NAME>"` |
| Push research-table rows to a chosen Instantly or Smartlead campaign | `autotouch columns recipe --type to_outreach` |
| Create or update Autotouch Leads from research-table rows | `autotouch columns recipe --type to_leads` |
| Enroll Autotouch Leads into an Autotouch Sequence | `autotouch columns recipe --type to_sequence` |

Notes:

- `from-crm` creates or appends to a research table from the external CRM; use
  `integrations recipe --type from_crm` only when you want the raw API path/body
  scaffold instead of submitting the import.
- `to_crm` writes to the external CRM; it is not the same as `to_leads`.
- `to_outreach` uploads to a chosen provider campaign; it is not the same as an Autotouch Sequence.
- use `autotouch outreach accounts list --provider instantly` to inspect eligible provider mailboxes before creating a campaign.
- use `autotouch outreach campaigns create --provider instantly --name "<NAME>" --min-health-score 100` when the provider campaign does not exist yet. Created campaigns stay draft/inactive by default.
- use `autotouch outreach campaigns activate --provider instantly --campaign-id <CAMPAIGN_ID>` only after the push sample looks correct.
- provider campaign templates are deterministic templates with provider/table variables. Use Autotouch Sequences when the user wants per-lead AI drafts, review tasks, LinkedIn steps, or rep assignment.
- for bulk Instantly/Smartlead sequences, choose the provider sender pool on the sequence with `autotouch sequences sync-accounts --sequence-id <SEQUENCE_ID> --account-selection all_eligible` or `--account-selection selected --selected-account sender@example.com`. Do this instead of syncing only the raw provider campaign when the campaign is managed by an Autotouch Sequence.
- `to_sequence` needs lead IDs. If the table only has raw person/contact fields, add `to_leads` first.
- easy path: set `to_sequence.config.assignedToUserId` to the task owner/reviewer and omit sender routing fields so that user's default connected sender accounts are used.
- team-managed path: use `autotouch sender-accounts list --channel email` and `autotouch sender-accounts list --channel linkedin`, then set `senderAccountIds` when the task owner/reviewer differs from the mailbox or LinkedIn sender.
- round-robin path: set `assignmentPlan` plus `routingMode=round_robin` / `weighted_round_robin` for multiple owners/senders.
- sender management path: owners/admins can use `autotouch sender-accounts set-default`, `grant-delegation`, and `revoke-delegation` when they are managing sender accounts on behalf of users. Do not require this for a normal "add leads to my sequence" flow.
- keep `to_sequence.config.skipActiveSequenceConflicts=true` so table-driven handoffs skip people already active in another sequence. Set it `false` only when the user explicitly wants parallel sequence enrollment.
- For one-off `autotouch sequences enroll`, inspect `sender_routing_hint`; use `--assigned-to-user-id` first, explicit channel sender ids only when pinning is required, and `--assignment-plan-json` plus `--routing-mode` only for team routing.

CRM round trip:

```bash
autotouch integrations list --output json
autotouch integrations status --provider hubspot

autotouch integrations from-crm \
  --provider hubspot \
  --list-id <LIST_ID> \
  --selected-field name \
  --selected-field domain \
  --table-name "HubSpot Accounts" \
  --output json
autotouch integrations from-crm-status --task-id <TASK_ID> --output json

autotouch columns recipe --type llm_enrichment --out-file enrich.json
autotouch columns recipe --type email_finder --out-file email-finder.json
autotouch columns recipe --type to_crm --out-file push-to-crm.json
autotouch columns create --table-id <TABLE_ID> --data-file push-to-crm.json
autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope filtered --wait --output json
```

### AI Buyer Discovery -> Sequence

Use when the goal is to find people from company rows and then enroll qualified leads:

1. `llm_enrichment` in agent mode
2. `formatter` or `split_json_fields` only when identity fields need shaping
   before downstream contactability runs
3. `email_finder` / `phone_finder` when the sequence has email or phone fallback paths
4. `add_to_crm` / Sync to Leads after person identity fields are stable
5. `add_to_sequence`

Notes:

- before Sync to Leads / Add to Sequence / HTTP Request / Sync to Table, call
  `read_provider_input_readiness` with the exact `operation_id` (and
  `sequence_id` when the destination sequence is known) so you use that
  action's declared input slots/mappings instead of inventing a parallel list
- use `researchMode: "lite"` for one clear lead/person per company
- use `researchMode: "heavy"` for 1-3 recommended people or ambiguous ownership
- if a people table has only a scalar person/name column, derive `first_name`
  and `last_name` with formatter columns before email finder
- if identity fields are nested inside JSON/object cells, project them with
  `split_json_fields` instead of formatter
- for `sync_to_leads` standard company fields, map `companyDomain` to the
  durable domain/match key and map `companyName` to the best human-readable
  company-name source. If the clean name exists only inside JSON, use the
  supported structured mapping, for example
  `{ "column": "company_name_clean", "path": "result" }`; do not map
  `companyName` to a domain-looking raw import field when a clean-name output
  is available.
- for email finder, prefer the `auto` path when a public LinkedIn URL exists
  and name plus domain/company fallback inputs are available
- make sure the target sequence already exists and is `ACTIVE`
- do not enroll before required contactability enrichments have completed
- keep `requiredVariables` aligned with variables used by the sequence templates or mapped from the research table. An unused required variable can fail provider enrollment even when the copy does not reference it.
- easy path: set `add_to_sequence.config.assignedToUserId` to the task owner/reviewer and let that user's default connected sender accounts run the sequence
- team-managed path: use `senderAccountIds` when the task owner/reviewer differs from the mailbox or LinkedIn sender
- round-robin path: use `assignmentPlan` plus `routingMode` for team routing across multiple owners/senders
- sender management path: owners/admins can set defaults or grant/revoke sender delegations with `autotouch sender-accounts` commands, but ordinary add-to-sequence setup should not need that
- keep `add_to_sequence.config.skipActiveSequenceConflicts=true` unless the user explicitly wants parallel enrollment for people already active in another sequence
- `sync_to_table` columns with `autoRun: "onSourceUpdate"` schedule an initial unprocessed-only backfill when saved; use explicit column runs only for repair, filtered backfills, or intentional reruns
- if a downstream column depends on another column's output, do not leave it on `onInsert`; use `onSourceUpdate` plus a guard like `company_name isNotEmpty`

### LLM contacts[] -> Contacts Table -> CRM -> Sequence

Use when the workflow needs custom research output:

1. `llm_enrichment` in agent mode
2. `sync_to_table` in `list` mode
3. `email_finder` on the destination contacts table
4. `add_to_crm`
5. `add_to_sequence`

Notes:

- canonical people-discovery list root is `contacts`; legacy `items`, `people`, or `leads` roots can still be synced by setting `listPath`
- for company-table people discovery, write the agent instructions around the
  desired responsibility or buyer role, for example `Find persons most
  responsible for sales operations at this company...`
- use item-scoped mappings for contact fields that come from each list item
- use row-scoped mappings for parent company metadata; nested JSON row fields use `sourceKey` plus `path`
- do not manually parse `autotouch rows list` output into local JSON and re-import it with `rows add`; use an action enrichment so the table keeps provenance, reruns, progress, and dedupe semantics
- if the upstream JSON uses a legacy array key such as `items`, set `sync_to_table.config.listPath` to that key
- when one condition should write both the parent company row and a child contacts list, use advanced `sync_to_table.config.routes[].actions[]` with one `kind: "row"` action and one `kind: "list"` action
- set `add_to_sequence.config.assignedToUserId` on the final handoff column so generated sequence tasks are assigned to the intended user
- keep `add_to_sequence.config.skipActiveSequenceConflicts=true` on the final handoff unless the user explicitly wants parallel sequence enrollment

### LLM contacts[] -> Leads CRM

Use when the workflow already has a structured contact/person object or array and the goal is to create or update Leads directly, not to review/enrich contacts in a separate destination table:

1. `llm_enrichment` in agent mode
2. `sync_to_leads` / internal `add_to_crm` in array mode
3. `add_to_sequence` if a sequence handoff is needed

Notes:

- start from `autotouch columns recipe --type sync_to_leads_array --out-file sync-to-leads-array.json`
- use `config.fieldMappings.leadSources[]` when the row contains both a primary buyer object and one or more people arrays
- use a `kind: "row"` lead source for one lead from parent-row fields; nested JSON mappings use `{ "column": "buyer_research", "path": "primary_buyer.first_name" }`
- use a `kind: "array"` lead source for one lead per list item; set `sourceColumn`, `sourceArrayPath`, and item mappings with `column: "__array_item"`
- keep `common` company mappings on the parent row, usually `domain` and `company_name`; these apply to every lead source
- prefer this direct enrichment over creating a throwaway local JSON file or importing copied contact rows by hand
- set `add_to_sequence.config.assignedToUserId` when adding the optional sequence handoff
- keep `add_to_sequence.config.skipActiveSequenceConflicts=true` when adding the optional sequence handoff unless the user explicitly wants parallel enrollment

## Controlled Execution Rules

- prefer `run-next` when you need up to `N` rows with a deterministic upper bound
- prefer `firstN + --unprocessed-only` for staged rollouts
- prefer `--output json`
- treat the canonical `operation_run` receipt as authoritative state for
  Research Table runs; bulk jobs are executor/debug projections
- do not infer success from local terminal output alone

## If an Agent Gets Stuck

Usually the next move should be one of:

- `autotouch auth check`
- `autotouch capabilities --output json`
- `autotouch rows get --table-id <TABLE_ID> --row-id <ROW_ID> --output json`
- `autotouch cells get --row-id <ROW_ID> --column-id <COLUMN_ID> --output json`
- `autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --wait --output json`

If the agent is still looping in docs/help after those, the product contract is probably not self-describing enough yet. That should be fixed in `capabilities`, provider contracts, or CLI inspection surface before adding more prose.

## Local Audit Harness

Use the local harness when you want a repeatable clean-room audit against a local backend without waiting for deploys:

```bash
python3 scripts/run_cli_local_audit.py --base-url http://localhost:8005
```

Behavior:

- blanks ambient developer/user auth vars before invoking the CLI
- uses an isolated config file under `/tmp`
- bootstraps a fresh account locally
- logs every command plus stdout/stderr
- writes a machine-readable summary after the run

For external-user parity instead of repo-local current-checkout testing:

```bash
python3 scripts/run_cli_local_audit.py \
  --base-url http://localhost:8005 \
  --cli-cmd autotouch \
  --workdir /tmp/autotouch-clean-room
```

That mode is closer to a real package user because the subprocesses do not inherit repo docs or repo `.env` state.
