RFP handling

Read inbound RFP documents from a central Sales folder, extract structured metadata into DataPool, resolve the customer from CRM (master), draft an offer as a tenant file, and require human approval before any customer send.

Document information
FieldValue
Canonical URL/docs/07_ai-agents-and-mcp/model_skills/20_sales/rfp-handling
Version (published date)2026-09-01
Tagsai, skills, sales, rfp, datapool, playbooks

Kind

Sequential procedure.

Depends on

tenant-data-model, crm-field-mapping. Optional: sales-process (link or create opportunity_id at proposal stage).

Prerequisites

idquestionsearchif_missing_or_ambiguousdefault_to_proposebinds_to
rfp_requests_schemaDataPool schema for RFP metadatatenant-data-modelImport tenant-data-model firstrfp_requestsTenant SKILL.md
incoming_rfp_folderFolder for inbound RFP fileslist_directory under tenantdata Sales/Ask; create folders if missingSales/Incoming-RFPsTenant SKILL.md
offers_folderFolder for draft offer fileslist_directory Sales/AskSales/OffersTenant SKILL.md
crm_integration_idCRM integration (customer master)crm-field-mappingImport and bind CRMNoneTenant SKILL.md
opportunities_schemaDataPool opportunities (optional link)tenant-data-modelImport firstopportunitiesTenant SKILL.md
sales_approver_user_idTenant user who approves offers before sendentity_search users / askrequest_human_inputNoneTenant SKILL.md
mail_integration_idMail integration for Trace-assisted send (optional)list_integrations Gmail / Outlook / IMAPAsk; leave empty if operator sends manuallyNoneTenant SKILL.md

Import contract

FieldValue
suggested_skill_idrfp-handling
version1.0.0
capabilities.toolslist_directory, read_file, write_file, pdf_extract_text, describe_datapool_table, query_datapool, insert_datapool_rows, update_datapool_rows, describe_tenant_integration, execute_tenant_integration, request_human_input, list_integrations
Paired model agentincoming-rfp-watcher

Procedure (generic B2B)

Source of truth split

DataMasterNotes
Customer account, contacts, deal historyCRM ({{crm_integration_id}})Never invent account ids; resolve via CRM search
RFP intake metadata, status, file pathsDataPool {{rfp_requests_schema}}Pointers only — not a second CRM
Draft offer bodyFile under {{offers_folder}}/{rfp_id}/Markdown or PDF; path stored on the row

Statuses: newextracteddraftingpending_approvalapprovedsent (or declined at any gate).

StageQuality gate to exit
newInbound file listed under {{incoming_rfp_folder}}; row exists with source_file_path
extractedRequired fields in requirements_summary (see DoD); CRM account resolved or explicitly crm_account_id=unknown with operator note
draftingDraft file written; draft_offer_path set
pending_approvalrequest_human_input / operator notified; no customer send
approvedApprover confirmed; approved_by_user_id and approved_at set
sentSend confirmed; sent_at set — only after approved

DoD — extracted (requirements_summary JSON text): at minimum customer_name, submission_deadline (or deadline_unknown=true), scope_summary, evaluation_criteria (array or string), mandatory_requirements (array or string). Optional: budget_hint, contact_email, reference_number.

DoD — draft offer file: sections for executive summary, scope response, commercial outline (line items or TBD with reason), assumptions, exclusions, validity period. Do not state final prices as committed until approved.

Send rules (mandatory human gate):

  1. Never email the customer without explicit operator confirmation in the same session.
  2. Trace: request_human_input with choices — Send via mail integration (only if {{mail_integration_id}} is bound), I will send manually, or Cancel.
  3. On manual send: operator marks status=sent after they sent outside Tealfabric.
  4. On Trace send: use execute_tenant_integration on the bound mail integration; attach or body from draft_offer_path; then set status=sent and sent_at.
  5. Autonomic agents: use decision need_human at pending_approval and before any send act — no execute_tenant_integration for outbound customer mail unless the operator resumed with explicit send approval.

Tenant SKILL.md (copy this body)

# RFP handling

Inbound folder: `{{incoming_rfp_folder}}`. Draft offers folder: `{{offers_folder}}`. DataPool schema: `{{rfp_requests_schema}}`.

CRM is the **customer master** (`{{crm_integration_id}}`). Load `integrations`, tenant skills `tenant-data-model` and `crm-field-mapping`, and platform skill `datapool`.

Sales approver user id: `{{sales_approver_user_id}}`. Optional mail integration for assisted send: `{{mail_integration_id}}` (empty = manual send only).

## Intake and extract

1. `list_directory` `{{incoming_rfp_folder}}` (non-recursive unless operator bound otherwise).
2. For each file without a matching `source_file_path` row in `{{rfp_requests_schema}}`:
   - `read_file` or `pdf_extract_text` as appropriate.
   - Insert row: `rfp_id` (new uuid), `source_file_path`, `status=new`, `owner_user_id` = approver if known.
3. Parse the document into `requirements_summary` JSON (DoD fields). Set `status=extracted`.
4. Resolve customer in CRM (`describe_tenant_integration`, search by company name). Set `crm_account_id` when found. Set `company_name` from CRM when it differs from the RFP header. Do **not** overwrite CRM records from RFP text alone.
5. Optionally link `opportunity_id` in `{{opportunities_schema}}` at `stage=proposal` (see `sales-process`).

## Draft offer

1. Create `{{offers_folder}}/{rfp_id}/offer.md` (or operator-approved extension) with `write_file`.
2. Set `draft_offer_path`, `status=drafting`, then `status=pending_approval`.
3. `request_human_input`: show `rfp_id`, `company_name`, deadline, path to draft, and summary. Ask approver to review the file and choose **Approve**, **Revise**, or **Decline**.

## Approval

- **Approve:** set `status=approved`, `approved_by_user_id`, `approved_at`. Do not send yet.
- **Revise:** keep or return to `drafting`; update file and re-enter `pending_approval`.
- **Decline:** set `status=declined` with reason in `requirements_summary` or a notes field if bound.

## Send (after approved only)

1. `request_human_input`: confirm recipient email (from CRM contact or RFP), subject, and send mode.
2. If operator chooses manual send: instruct them to send from their mail client; after confirmation set `status=sent`, `sent_at`.
3. If operator chooses Trace send and `{{mail_integration_id}}` is set: `execute_tenant_integration` with draft content; on success set `status=sent`, `sent_at`.
4. Never send when `status` is not `approved`.

## Stop

- Do not invent CRM account or contact ids.
- Do not copy full CRM payloads into DataPool — store ids and extracted RFP facts only.
- Do not skip `pending_approval` or send without confirmation.
- Do not delete inbound RFP files without operator confirmation.

Optional skill.json

{
  "name": "rfp-handling",
  "version": "1.0.0",
  "description": "Ingest RFPs from Sales storage, extract metadata, draft offers, human approval before customer send.",
  "capabilities": {
    "prompt": [
      "CRM is customer master. Draft offers are files; metadata in DataPool.",
      "Never send to customer without explicit human approval."
    ],
    "resources": [],
    "tools": [
      "list_directory",
      "read_file",
      "write_file",
      "pdf_extract_text",
      "describe_datapool_table",
      "query_datapool",
      "insert_datapool_rows",
      "update_datapool_rows",
      "describe_tenant_integration",
      "execute_tenant_integration",
      "list_integrations",
      "request_human_input"
    ]
  }
}

See also