Directory and CRM

The directory family is the workspace's canonical registry of external parties — the people and organisations every other desk resolves its counterparties through. The crm family is the revenue desk built on top of it: deals, accounts, playbooks, and escalations. Scopes: directory.read / directory.write and crm.read / crm.write.

Awareness scoping — the Directory's core rule

Directory reads are awareness-scoped at the query layer. A record marked named-only is absent from every result for a seat without a grant — not redacted, not hinted at, not countable. Two agents can run the same directory.search and correctly receive different result sets. Design for it: an empty result means "nothing you are allowed to know about", never "nothing exists".

Query the Directory

curl -X POST https://os.cohortapp.com/api/v1/directory.search \
  -H "Authorization: Bearer $COHORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"params": {"q": "acme", "limit": 10}}'

Reads: search, listPeople, listOrgs, getPerson, getOrg, listLists, listCaptures, listMerges, listAwarenessRequests, plus ask (a grounded question over the Directory) and exportVcard — both audited side-effecting reads.

Writes: createPerson / updatePerson, createOrg / updateOrg, contact channels (addChannel, removeChannel, setPrimaryChannel), links (linkParty, unlinkParty), lists (createList, addToList, removeFromList, shareList), and captures (proposeCapture, resolveCapture) — the intake lane that turns an observed counterparty into a record.

Structural verbs are human-only

executeMerge, revertMerge, setAwareness, archivePerson, and archiveOrg refuse an agent actor in the domain layer — the call creates a review item for a human instead of mutating. Agents propose (proposeMerge, requestAwareness); humans dispose. Expect a result that says a review was filed, not a completed mutation.

Run the revenue desk

The crm family (44 methods) mirrors the app's deal surfaces exactly — including its holds.

| Task | Methods | | --- | --- | | Deal lifecycle | createDeal, updateDeal, moveStage, closeDeal, deleteDeal | | Terms and playbooks | proposeTerms, updateHeldTerms, holdNextStep, runNextBestAction, savePlaybook, togglePlaybook, deletePlaybook, compilePlaybook (a pure compile preview — persists nothing) | | Sharing and delegation | shareDeal, revokeDealShare, mintGuestToken, addCallGuest, delegate | | Accounts and contacts | createAccount, updateAccount, archiveAccount, createContact, updateContact, deleteContact, linkContact | | Record keeping | logInteraction, linkEmail, linkFiles, pinNote, redactInteraction, logDealDecision, taskFollowThrough | | Escalations and targets | signEscalation, returnEscalation, setTarget | | Reads | listDeals, getDeal, listEscalations, listAccounts, listContacts, listInteractions, listPlaybooks, getTargets, ask |

Playbook holds return as results

crm.proposeTerms is the enforcement path for concessions. When proposed terms cross a playbook line — too deep a discount, a non-standard term — the call succeeds: the deal is placed on hold, the interaction log records it, and the result carries the hold:

{
  "ok": true,
  "result": {
    "dealId": "deal_…",
    "held": true,
    "playbookSlug": "discount-floor",
    "reason": "Discount beyond 20% needs a signature.",
    "marginImpactCents": -420000,
    "ledgerId": "ab12cd34"
  }
}

Report it exactly as it is — "held for signature — ledger #ab12cd34" — and never as a completed concession. A human releases the hold from the deal surface (or an agent updates the held terms with updateHeldTerms to bring them inside the line).

Two verbs are human-signature acts in the domain layer regardless of scope: signEscalation / returnEscalation (deciding an escalated deal) and setTarget's targetCents (setting a revenue target). An agent-plane call to these refuses — the same rule the app enforces.

Contact cards vs. the Directory

The older contact family (contact.add / update / delete / list) manages contact cards on org content, and contacts.upsert feeds the knowledge-plane contact registry. For external people and companies, the Directory is the canonical registry — prefer it; the other two remain for their in-app surfaces.