@audienti/cli 0.2.0-exora.4 → 0.2.0-exora.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +2 -2
  2. package/cli/account-reply-operations.mjs +24 -1
  3. package/cli/agents.mjs +42 -10
  4. package/cli/audience-operations.mjs +1 -1
  5. package/cli/audienti.mjs +29 -16
  6. package/cli/campaign-find.mjs +155 -0
  7. package/cli/campaign-operations.mjs +6 -3
  8. package/cli/campaigns.mjs +21 -3
  9. package/cli/cohort-operations.mjs +1 -1
  10. package/cli/discovery-operations.mjs +1 -0
  11. package/cli/discovery.mjs +2 -1
  12. package/cli/kv-operations.mjs +17 -0
  13. package/cli/kv.mjs +58 -0
  14. package/cli/mcp.mjs +2 -2
  15. package/cli/outcome-operations.mjs +19 -0
  16. package/cli/outcomes.mjs +16 -0
  17. package/cli/owner-account-client.mjs +1 -1
  18. package/cli/{approach-operations.mjs → playbook-operations.mjs} +15 -15
  19. package/cli/progress.mjs +6 -6
  20. package/cli/question-operations.mjs +4 -4
  21. package/cli/questions.mjs +4 -4
  22. package/cli/queue-operations.mjs +7 -6
  23. package/cli/queue.mjs +4 -3
  24. package/cli/recipient-operations.mjs +1 -1
  25. package/cli/rooms-operations.mjs +4 -4
  26. package/cli/rooms.mjs +4 -4
  27. package/cli/secret-operations.mjs +7 -0
  28. package/cli/secrets.mjs +30 -0
  29. package/cli/sender-account-operations.mjs +6 -2
  30. package/cli/sender-accounts.mjs +13 -8
  31. package/cli/skills.mjs +5 -5
  32. package/cli/tools-operations.mjs +18 -10
  33. package/cli/tools.mjs +48 -20
  34. package/cli/work-execution-operations.mjs +6 -0
  35. package/cli/work-execution.mjs +3 -3
  36. package/cli/work-operations.mjs +4 -3
  37. package/cli/work.mjs +2 -2
  38. package/cli/workspaces.mjs +24 -14
  39. package/package.json +2 -2
  40. package/skills/agent-rooms/SKILL.md +13 -11
  41. package/skills/agent-rooms/references/commands.md +2 -2
  42. package/skills/audienti/SKILL.md +75 -0
  43. package/skills/getting-started/SKILL.md +178 -158
  44. package/skills/getting-started/references/audience.md +72 -7
  45. package/skills/getting-started/references/campaign-draft.md +51 -11
  46. package/skills/getting-started/references/capabilities.md +214 -0
  47. package/skills/getting-started/references/mailbox-check.md +64 -0
  48. package/skills/getting-started/references/outcome-checks.md +89 -0
  49. package/skills/getting-started/references/replies.md +152 -0
  50. package/skills/getting-started/references/reply-checks.md +54 -0
  51. package/skills/playbook-design/SKILL.md +132 -0
  52. package/skills/{approach-design → playbook-design}/references/fields.md +28 -22
  53. package/source-manifest.json +53 -42
  54. package/skills/approach-design/SKILL.md +0 -104
  55. package/skills/getting-started/references/inventory.md +0 -52
@@ -4,12 +4,24 @@ An audience is a set of structured filters plus one smart rule. Discovery and fi
4
4
  read the structured filters first, so put everything you can into them. Free text is
5
5
  only for the one test the filters cannot express.
6
6
 
7
+ An audience says **who** the buyer is: facts about the company and the person that stay
8
+ true for months. It never says **why now**. Hiring, funding, a new leader, a tool
9
+ change, a post or an intent to buy are events, and events are campaign signals (see
10
+ section 5). An audience with an event in it matches almost no one.
11
+
7
12
  ## 1. Pick one buyer lane
8
13
 
9
14
  One audience describes one kind of buyer: one function at one seniority band, in one
10
15
  kind of company. If the offer sells to two different buyers (for example finance and
11
16
  operations), draft two audiences and two campaigns. Do not mix lanes in one audience.
12
17
 
18
+ Every audience sets `person.functions`. Seniority alone is not a lane: "all VPs" reaches
19
+ the VP of Finance and the VP of Engineering as well as the buyer. Pick the function that
20
+ owns the problem the offer solves and signs for it. For example, an outbound or SDR
21
+ agency sells to whoever owns pipeline: Sales, and Marketing when marketing owns
22
+ pipeline. Add `currentTitles` such as "VP of Sales" or "Chief Revenue Officer" when the
23
+ function is still too wide.
24
+
13
25
  ## 2. Resolve every value with `audience options`
14
26
 
15
27
  Never type a value from memory. Look each one up and copy the `providerId` and `name`
@@ -24,8 +36,19 @@ audienti audience options --kind functions --q finance
24
36
  audienti audience options --kind titles --q "head of finance"
25
37
  ```
26
38
 
27
- Kinds: `industries`, `industry_groups`, `sizes`, `types`, `locations`, `seniorities`,
28
- `functions`, `titles`. Over MCP, use `audience_options` with the same `kind`.
39
+ Kinds: `industries`, `sizes`, `types`, `locations`, `seniorities`, `functions`, `titles`.
40
+ Over MCP, use `audience_options` with the same `kind`. Name industries one by one: new
41
+ audiences cannot add industry groups, because a group's industries do not match its name.
42
+
43
+ - Search ignores case. Several terms in one query ("freight and logistics", or a
44
+ comma-separated list) return the matches for each term.
45
+ - An industry search matches the industry's own name, not the group it sits in:
46
+ "logistics" returns the logistics industries, not airlines or rail.
47
+ - Company size comes in LinkedIn's bands. A range in `--q` ("100 to 1000", "500+",
48
+ "under 50") returns every band it overlaps. Add all of them to `company.sizes`.
49
+ - `functions` is LinkedIn's fixed list of 26 job functions. It needs no Sales Navigator
50
+ account.
51
+ - A title that is not in the list can be saved as typed, without a `providerId`.
29
52
 
30
53
  ## 3. Fill the template
31
54
 
@@ -33,7 +56,7 @@ Kinds: `industries`, `industry_groups`, `sizes`, `types`, `locations`, `seniorit
33
56
  {
34
57
  "name": "Heads of finance, UK logistics, 51-500 staff",
35
58
  "company": {
36
- "industries": [{ "providerId": "ID", "name": "Transportation, Logistics, Supply Chain and Storage" }],
59
+ "industries": [{ "providerId": "ID", "name": "Logistics and Supply Chain" }, { "providerId": "ID", "name": "Package/Freight Delivery" }],
37
60
  "sizes": [{ "providerId": "ID", "name": "51-200" }, { "providerId": "ID", "name": "201-500" }],
38
61
  "locations": [{ "providerId": "ID", "name": "United Kingdom" }]
39
62
  },
@@ -45,11 +68,14 @@ Kinds: `industries`, `industry_groups`, `sizes`, `types`, `locations`, `seniorit
45
68
  },
46
69
  "seniorityMatchMode": "at_least",
47
70
  "textCriteria": "The company runs its own truck or freight fleet.",
48
- "notes": "Region and size inferred from the owner's last 20 sent emails; owner confirmed in Operator."
71
+ "notes": "Region and size inferred from the user's last 20 sent emails; user confirmed in Operator."
49
72
  }
50
73
  ```
51
74
 
52
75
  - `seniorityMatchMode: "at_least"` matches the chosen seniority and higher.
76
+ - After saving, `audienti audience summary --audience ID` returns `sizes`: about how many
77
+ people the included industries, functions and regions reach, against the 1,000-person
78
+ minimum. Under 1,000, widen the industries, functions or regions.
53
79
  - Use `negative: true` on a value to exclude it (for example a competitor's industry).
54
80
  - `currentTitles` narrows the lane; keep it to the titles that buy, at most a few.
55
81
 
@@ -67,12 +93,51 @@ Good:
67
93
  Not allowed in the smart rule:
68
94
 
69
95
  - Provenance: "inferred from…", "based on the website", "found in sent email".
70
- - Hedges or open questions: "owner to confirm", "probably", "maybe", "TBD".
71
- - Values the filters already hold (industry, size, region, seniority, title).
96
+ - Hedges or open questions: "user to confirm", "probably", "maybe", "TBD".
97
+ - Values the filters already hold (industry, size, region, seniority, function, title).
72
98
  - More than one test joined with "and" or "or". Pick the one that matters most.
99
+ - Events, timing or intent: "is hiring…", "recently raised…", "is considering…",
100
+ "is looking for…", "just changed…". These are campaign signals (section 5).
73
101
 
74
- Put provenance in `notes`. Put open questions to the owner in Operator with
102
+ Put provenance in `notes`. Put open questions to the user in Operator with
75
103
  `audienti ask`, not in the audience.
76
104
 
105
+ Saving refuses a smart rule that states the company's size or an event, and says where
106
+ it belongs: size in `company.sizes`, events in a campaign signal.
107
+
77
108
  Check the rule before you save: `audienti audience improve --text "RULE"` returns a
78
109
  suggested rewrite when the rule is vague.
110
+
111
+ ## 5. Audience or signal?
112
+
113
+ Ask of each condition: would it still be true of this company in six months?
114
+
115
+ | Condition | Where it goes |
116
+ | --- | --- |
117
+ | Sells B2B software; 51 to 500 staff; based in the US | Audience filters |
118
+ | Sells to hospitals; runs its own freight fleet | Audience smart rule |
119
+ | Owns pipeline or revenue (VP of Sales, CRO) | Audience `person.functions`, `seniorities`, `currentTitles` |
120
+ | Is hiring SDRs; is considering an outbound agency | Campaign signal |
121
+ | Raised a round this quarter; new VP of Sales in the last 90 days | Campaign signal |
122
+
123
+ A signal lives in the campaign's `signals` array. A `research` signal is a yes/no
124
+ question a researcher answers from public evidence such as job posts, news or the
125
+ company's site:
126
+
127
+ ```json
128
+ {
129
+ "signalMatch": "any",
130
+ "signals": [{
131
+ "id": "UUID",
132
+ "kind": "research",
133
+ "scope": "company",
134
+ "question": "Is the company hiring SDRs or BDRs, or looking for an outbound agency?",
135
+ "expected": true,
136
+ "details": "Open SDR or BDR job posts, or a post asking for agency recommendations, in the last 90 days."
137
+ }]
138
+ }
139
+ ```
140
+
141
+ `signalMatch: "any"` lets a company qualify on any one signal; `"all"` needs every one.
142
+ A signal narrows who is worth contacting now; it never replaces the audience, so the
143
+ company and person still have to fit the audience.
@@ -1,23 +1,44 @@
1
1
  # Campaign draft
2
2
 
3
- Fill this before you ask the owner anything. Mark each value with where it came from:
4
- `found` (quote or link), `inferred` (say from what) or `needed` (ask the owner).
3
+ Fill this before you ask the user anything. Mark each value with where it came from:
4
+ `found` (quote or link), `inferred` (say from what) or `needed` (ask the user).
5
5
 
6
6
  | Field | Where it goes | How to fill it |
7
7
  | --- | --- | --- |
8
- | Offer: what you sell | Offer | Website, pitch deck, proposals, email signature. Owner's words. |
9
- | Offer: problem it solves | Offer | Website, sent email, call notes. Owner's words. |
10
- | Offer: proof point | Offer | Only a result or customer the owner already states publicly or confirms. |
8
+ | Offer: what you sell | Offer | Website, pitch deck, proposals, email signature. User's words. |
9
+ | Offer: problem it solves | Offer | Website, sent email, call notes. User's words. |
10
+ | Offer: proof point | Offer | Only a result or customer the user already states publicly or confirms. |
11
11
  | Company type, size, region | Audience filters | Current customers in the CRM or sent email; check values with `audience options`. |
12
- | Buyer title and seniority | Audience filters | Who replies to the owner or signs deals. |
12
+ | Buyer function, seniority and title | Audience filters | The function that owns the problem the offer solves, then who replies to the user or signs deals. Always set a function. |
13
13
  | Who to avoid | Do-not-contact list | Current customers, partners, competitors (`dnc add` or `dnc import`). |
14
- | Premise | Campaign premise | One sentence: why these people, why now. |
15
- | Approach | Campaign approach pin | One approach from `approach list`, or a new one drafted with the approach-design skill, with one line on why. |
14
+ | Signals | Campaign `signals` | At least one. Events that make a company worth contacting now: hiring for a role, funding, a new leader, a tool change. Each one is a yes/no `research` question ([audience definition](audience.md) section 5). Never put these in the audience. |
15
+ | Premise | Campaign premise | One sentence: why these people, why now. It names the signal and why the signal means they need the offer. |
16
+ | Playbook | Campaign playbook pin | One playbook from `playbook list`, or a new one drafted with the playbook-design skill, with one line on why. When the list returns a `recommendation`, start from that starter playbook and name it with its `reason` in one line. |
16
17
  | Channels | Campaign channels | `email` and `linkedin`, only for accounts you reported. |
17
- | Test length | Campaign `schedule.endAt`, `schedule.maxCompanies` | 30 days from activation, or a company cap. |
18
- | Success measure | Approach goal stage | Meetings booked. |
18
+ | Pace | Campaign pace | New people a day. Take the number and its reason from the **Pace line** in your queue task for the channels you propose: 25 a day per confirmed LinkedIn account, or the campaign's own number (50 by default) for email only. Write it as `Pace: 25 a day · 1 LinkedIn account · about 100 people a week`. Once the campaign is saved, `audienti campaign pace ID` returns its own line: use that line, so the card and the campaign page show the same number. The user can change it in their answer. |
19
+ | Test length | Campaign `schedule.endAt`, `schedule.maxCompanies` | 30 days from activation or a company cap, whichever comes first. |
20
+ | Success measure | Playbook goal stage | Meetings booked. |
21
+ | Waits | Playbook step `wait` | What each step waits for (`accept`, `reply`, `meeting` or `timeout`) and for how many days; their sum fits the test length. A wait starts only when the agent reports a succeeded result for the plan action that carries the step's key as `playbookStepKey`, and any reply ends every wait (playbook-design `references/fields.md`). |
19
22
 
20
- Ask for at most two `needed` values in your first message. Fill the rest after the owner
23
+ ## Signals: the through line
24
+
25
+ Prospect finding starts from signals: they find the companies that need the offer
26
+ today. A campaign with no signal and no target list cannot start, so every campaign you
27
+ draft has at least one. A signal is not "they might want to buy". It is an
28
+ observable event that shows a company has the problem the offer solves, now. Build the
29
+ line in this order:
30
+
31
+ 1. **Offer**: what it solves. "An outbound agency books meetings for B2B software teams."
32
+ 2. **Problem**: who has it. "The company needs pipeline and has no working outbound team."
33
+ 3. **Signal**: the event that shows the problem today. "The company is hiring SDRs or
34
+ BDRs": it has decided to invest in outbound but does not have the team yet.
35
+ 4. **Premise**: one sentence that joins them. "Companies hiring their first SDRs need
36
+ pipeline before the hires ramp; the agency books meetings in weeks."
37
+
38
+ If you cannot name the event that shows the problem, ask the user what tells them a
39
+ company needs the offer now. Do not leave the signals empty.
40
+
41
+ Ask for at most two `needed` values in your first message. Fill the rest after the user
21
42
  replies.
22
43
 
23
44
  Name the campaign after the buyer and the test, for example "Heads of finance,
@@ -31,8 +52,27 @@ name or notes.
31
52
  | Offer | `audienti offer create --file OFFER.json --request-id UUID` | `offer_create` |
32
53
  | Audience | `audienti audience create --file AUDIENCE.json --request-id UUID` | `audience_create` |
33
54
  | Campaign | `audienti campaign create --file CAMPAIGN.json --request-id UUID` | `campaign_create` |
55
+ | Pace | `audienti campaign pace ID --per-day derived`, or `--per-day N` with the user's number | `campaign_pace_set` |
34
56
  | Next test | `audienti campaign successor ID --file CAMPAIGN.json --request-id UUID` | `campaign_successor` |
35
57
 
36
58
  Read each command's request fields in the API schema at `/api/v1/openapi.json`
37
59
  (search for the MCP tool name above). A campaign also needs the `audienceRevision`
38
60
  and `offerRevision` returned when you saved the audience and offer.
61
+
62
+ ## Example proposal
63
+
64
+ The shape of a first proposal, not fixed copy:
65
+
66
+ > I'm Arthur, your prospecting agent in Audienti. I find people who fit, research them
67
+ > and draft first messages for you to review. I can use Gmail (read, draft), LinkedIn
68
+ > through Composio, and web search. No CRM is connected, so records stay in Audienti.
69
+ > Proposed first test, 30 days. Offer: late-invoice recovery for logistics firms (from
70
+ > your website). Audience: finance function, Director and above, logistics companies,
71
+ > 51 to 500 staff, UK (region inferred from your last 20 sent emails). Smart rule: the
72
+ > company runs its own truck or freight fleet. Signal: the company is hiring a credit
73
+ > controller or accounts receivable clerk. Premise: a company hiring for collections
74
+ > has late invoices piling up now, and recovery gets cash back faster than a new hire. Playbook: problem-led email, then LinkedIn
75
+ > follow-up. Pace: 25 a day · 1 LinkedIn account · about 100 people a week. Success:
76
+ > meetings booked. Two questions: is the UK the right first region, and is there a
77
+ > customer result I may cite? Answer "Go" to save it as a draft, or write your changes.
78
+ > Nothing is sent until you approve it.
@@ -0,0 +1,214 @@
1
+ # Capability report format
2
+
3
+ Report what you can do for the user right now, as rows. One row per capability,
4
+ provider, source and account. The capability ids come from Audienti's catalog
5
+ ([ADR 015](https://github.com/omalab/exora/blob/main/docs/decisions/015-capability-taxonomy.md));
6
+ the full list is below.
7
+
8
+ ```json
9
+ {
10
+ "runtime": { "name": "Claude Code", "model": "claude-opus-5-5" },
11
+ "capabilities": [
12
+ { "capability": "web.search", "provider": "web", "via": "native", "access": "read", "status": "connected" },
13
+ { "capability": "web.page.read", "provider": "web", "via": "native", "access": "read", "status": "connected" },
14
+ { "capability": "profile.message.send", "provider": "email", "via": "composio:gmail", "account": "owner@example.com", "access": "send", "status": "connected" },
15
+ { "capability": "profile.inbox.read", "provider": "email", "via": "composio:gmail", "account": "owner@example.com", "access": "read", "status": "connected" },
16
+ { "capability": "profile.read", "provider": "linkedin", "via": "composio:linkedin", "access": "read", "status": "connected" },
17
+ { "capability": "profile.message.send", "provider": "linkedin", "via": "composio:linkedin", "account": "linkedin.com/in/owner", "access": "send", "status": "connected", "limits": { "daily": 20 } },
18
+ { "capability": "crm.records.search", "provider": "salesforce", "via": "mcp:salesforce", "access": "read", "status": "needs_sign_in" }
19
+ ]
20
+ }
21
+ ```
22
+
23
+ ```sh
24
+ audienti agent capabilities report --file CAPABILITIES.json --profile AGENT_PROFILE
25
+ audienti agent capabilities --profile AGENT_PROFILE
26
+ ```
27
+
28
+ The first command replaces your previous report. The second prints what Audienti now
29
+ counts for you: the rows that work now, and the rows that work with Audienti's hosted
30
+ tools in the selected workspace. Over MCP, use `agent_capabilities_report` and
31
+ `agent_capabilities_list`. Over HTTP, `PUT` and `GET /api/v1/agent-capabilities`.
32
+
33
+ ## Fields
34
+
35
+ - `runtime.name`: your product or runtime as the user knows it. `runtime.model` is
36
+ optional.
37
+ - `capability`: an id from the list below. An unknown id is refused.
38
+ - `provider`: which kind of profile the work touches. Required where the list names
39
+ providers; leave it out where it says "none". Where it says "any", leave it out or
40
+ name the one you use.
41
+ - `via`: where you reach it. `native` for a connector built into your runtime;
42
+ `composio:<app>` (for example `composio:gmail`), `treg:<tool>` or `mcp:<server>` for
43
+ a hub or MCP server. Check your hubs first: list each app connected there as its own
44
+ row.
45
+ - `account`: the user's own mailbox, LinkedIn account or handle you act through, such as
46
+ `owner@example.com` or `linkedin.com/in/owner`. Required for every verb that acts as
47
+ the user (marked "as the user" below).
48
+ - `access`: `read`, `write` or `send`, as listed for the capability. `send` reaches
49
+ another person; drafting is `write`.
50
+ - `status`: `connected` when you can call it now, `needs_sign_in` when it is installed
51
+ but signed out, `unavailable` when it exists but fails.
52
+ - `cost`: leave it out. Your own tools are `owner`.
53
+ - `limits`: optional `daily` and `hourly` caps you respect.
54
+ - `identity`: optional, on an email or LinkedIn row that names an `account`. The
55
+ account's own stable id, as `{ "namespace": "...", "accountId": "..." }`, so Audienti
56
+ can match the LinkedIn account or mailbox to the one the user set up. Use the same identity on
57
+ every row for that account. Examples: `{ "namespace": "linkedin", "accountId":
58
+ "ACoAAB1234" }` for a LinkedIn account, `{ "namespace": "gmail", "accountId":
59
+ "108234567890" }` for a Gmail mailbox.
60
+
61
+ At most 64 rows and 32 KiB.
62
+
63
+ ## LinkedIn accounts
64
+
65
+ For each LinkedIn account you send as, add an entry to `seats` with what you know about
66
+ it. Audienti shows it to the user on the LinkedIn account's page and plans around it.
67
+
68
+ ```json
69
+ {
70
+ "seats": [
71
+ {
72
+ "provider": "linkedin",
73
+ "account": "linkedin.com/in/owner",
74
+ "checkEverySeconds": 21600,
75
+ "inmail": { "available": true, "quotaPerMonth": 15 },
76
+ "limits": { "connectionRequestsPerDay": 20, "connectionRequestsPerWeek": 100, "messagesPerDay": 50, "inmailPerMonth": 15 }
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ - `account`: the same LinkedIn account you name in your LinkedIn rows.
83
+ - `checkEverySeconds`: how often you can check this LinkedIn account's sent invitations, at least
84
+ 3600 (one hour). Audienti schedules outcome checks at this cadence; every 6 hours
85
+ when you leave it out. The user never changes it.
86
+ - `inmail`: whether the LinkedIn account can send InMail, and its monthly quota. Report
87
+ `profile.inmail.send` as well; a LinkedIn account counts as InMail-capable only with both.
88
+ - `limits`: the safe limits for this LinkedIn account. Audienti never plans more LinkedIn sends on
89
+ it than its per-day and per-week limits. The user can lower a limit, never
90
+ raise it above the one you report.
91
+
92
+ Report `profile.invitations.read` on the LinkedIn account too, so outcome checks can be assigned to
93
+ you.
94
+
95
+ ## Rules
96
+
97
+ - **Report, never connect.** List only what you can call now. Do not install or sign in
98
+ to anything to fill the list.
99
+ - **Never invent.** Leave out anything you cannot call. A wrong row sends tasks to you
100
+ that you cannot do.
101
+ - **Never report `via: audienti`.** Audienti adds rows for its own hosted tools itself
102
+ when the workspace has credits; a report that sends `audienti` is refused. Leave out
103
+ Audienti's hosted tools unless you reach them through another connector.
104
+ - **Never include secrets** (tokens, cookies, keys) or personal data about the user's
105
+ contacts.
106
+ - Report again when your tools change, and at least every seven days. The user sees
107
+ older rows as stale.
108
+
109
+ ## The catalog
110
+
111
+ Launch rows can be required by queue tasks. Later rows are accepted and shown to the
112
+ user, but no task requires them yet.
113
+
114
+ ### People and companies
115
+
116
+ | Capability | Providers | Access | What it does | Launch |
117
+ | --- | --- | --- | --- | --- |
118
+ | `people.search` | any | read | Find people from audience filters or a question | yes |
119
+ | `people.discover` | any | read | Find look-alikes from a seed person or company | yes |
120
+ | `person.profiles.discover` | any | read | Expand one person into their other profiles | yes |
121
+ | `companies.search` | any | read | Find companies from filters | yes |
122
+ | `company.enrich` | any | read | Fill size, industry, location, site | yes |
123
+ | `company.posts.list` | linkedin, x, instagram, tiktok | read | What a company page has posted | yes |
124
+
125
+ ### Profile: research
126
+
127
+ | Capability | Providers | Access | What it does | Launch |
128
+ | --- | --- | --- | --- | --- |
129
+ | `profile.read` | linkedin, x, instagram, tiktok, github, web | read | Fetch one profile by URL or handle | yes |
130
+ | `profile.enrich` | linkedin, email | read | Fill title, company, location, history from one handle | yes |
131
+ | `profile.find` | email, phone | read | Find a person's handle on this provider | yes |
132
+ | `profile.verify` | email | read | Check the handle is live and deliverable | yes |
133
+ | `profile.posts.list` | linkedin, x, instagram, tiktok | read | What this profile has posted | yes |
134
+ | `profile.comments.list` | linkedin, x, instagram, tiktok | read | Comments this profile wrote on others' posts | yes |
135
+ | `profile.reactions.list` | linkedin, x, instagram, tiktok | read | Reactions this profile gave | yes |
136
+
137
+ ### Profile: as the user (needs `account`)
138
+
139
+ | Capability | Providers | Access | What it does | Launch |
140
+ | --- | --- | --- | --- | --- |
141
+ | `profile.message.send` | email, linkedin, whatsapp, phone, x, instagram | send | Send an approved message | email, linkedin |
142
+ | `profile.inbox.read` | email, linkedin, whatsapp | read | Watch the user's inbox for replies | email, linkedin |
143
+ | `profile.invitations.read` | linkedin | read | Read the user's sent invitations, to see which were accepted or closed | yes |
144
+ | `profile.inmail.send` | linkedin | send | Send an approved InMail | yes |
145
+ | `profile.connection.request` | linkedin | send | Send an approved connection request | yes |
146
+ | `profile.connection.withdraw` | linkedin | send | Withdraw a pending request | yes |
147
+ | `profile.connection.remove` | linkedin | send | Remove a connection | later |
148
+ | `profile.follow` | linkedin, x, instagram, tiktok | send | Follow a person or company | linkedin |
149
+ | `profile.unfollow` | linkedin, x, instagram, tiktok | send | Unfollow | linkedin |
150
+ | `profile.mute` | x, instagram | send | Mute | later |
151
+ | `profile.block` | x, instagram, linkedin | send | Block | later |
152
+ | `profile.call` | phone | send | Call a prospect | later |
153
+
154
+ ### Posts and topics
155
+
156
+ | Capability | Providers | Access | What it does | Launch |
157
+ | --- | --- | --- | --- | --- |
158
+ | `topic.track` | any | read | Track a topic for signals | yes |
159
+ | `posts.search` | linkedin, x, instagram, tiktok, web | read | Find posts about a topic | yes |
160
+ | `post.read` | linkedin, x, instagram, tiktok | read | Fetch one post with its type | yes |
161
+ | `post.comments.list` | linkedin, x, instagram, tiktok | read | Who commented, and what they said | yes |
162
+ | `post.reactions.list` | linkedin, x, instagram, tiktok | read | Who reacted | yes |
163
+ | `post.comment` | linkedin, x, instagram, tiktok | send | Comment as the user (as the user) | later |
164
+ | `post.react` | linkedin, x, instagram, tiktok | send | React as the user (as the user) | later |
165
+ | `post.publish` | linkedin, x, instagram, tiktok | send | Publish a post as the user (as the user) | later |
166
+
167
+ ### Web, calendar, records, owner, integration
168
+
169
+ | Capability | Providers | Access | What it does | Launch |
170
+ | --- | --- | --- | --- | --- |
171
+ | `web.search` | web | read | Search the web | yes |
172
+ | `web.page.read` | web | read | Read one page | yes |
173
+ | `calendar.read` | google, microsoft | read | Free and busy time | later |
174
+ | `calendar.freebusy.read` | google, microsoft | read | Read the user's free and busy times to offer slots in a reply | later |
175
+ | `calendar.event.create` | google, microsoft | write | Create an event and send the invite from the user's calendar | later |
176
+ | `meeting.book` | google, microsoft | write | Book or propose a meeting | later |
177
+ | `meeting.reschedule` | google, microsoft | write | Move a booked meeting | later |
178
+ | `meeting.cancel` | google, microsoft | write | Cancel a booked meeting | later |
179
+ | `crm.records.search` | hubspot, salesforce, pipedrive, other | read | Find records in the user's CRM | yes |
180
+ | `crm.record.read` | hubspot, salesforce, pipedrive, other | read | Read one CRM record | yes |
181
+ | `crm.record.write` | hubspot, salesforce, pipedrive, other | write | Write back to one CRM record | later |
182
+ | `owner.notify` | push, slack, sms, email | send | Reach the user on their own channel | yes |
183
+ | `webhook.receive` | none | read | Take a push instead of polling | later |
184
+ | `webhook.send` | none | send | Post events to the user's URL | later |
185
+
186
+ ### Tools you use for yourself
187
+
188
+ | Capability | Providers | Access | What it does | Launch |
189
+ | --- | --- | --- | --- | --- |
190
+ | `tool.media.transcribe` | none | read | Video or audio to text | later |
191
+ | `tool.image.read` | none | read | Read text and content out of an image | later |
192
+ | `tool.proxy.session.start` | none | write | Sticky residential session for your own browser, metered by bandwidth | yes |
193
+ | `tool.proxy.session.rotate` | none | write | New IP on the current session | yes |
194
+ | `tool.proxy.config.read` | none | read | Read region, stickiness and bandwidth cap | yes |
195
+ | `tool.proxy.config.write` | none | write | Set region, stickiness and bandwidth cap | yes |
196
+
197
+ Your runtime's documents, spreadsheets, files, code, memory, tasks and chat are not
198
+ capabilities. Leave them out.
199
+
200
+ ## Older reports
201
+
202
+ `audienti agent inventory report` (categories such as `web_search` or `crm`) and the
203
+ per-account `audienti agent capabilities report` with `provider` and `accountRef` are
204
+ still accepted for one release. Audienti translates both into rows. Send the format above
205
+ instead.
206
+
207
+ Translated inventory reports match fewer queue tasks than before:
208
+
209
+ - `company_data` alone matches none of the find, verify or enrich tasks.
210
+ - `linkedin` alone no longer matches find or enrich tasks.
211
+ - `web_search` alone no longer matches enrich tasks.
212
+ - `people_data` alone no longer matches verify tasks.
213
+ - `email` and `linkedin` match send tasks only for their own provider and only with
214
+ `send` access; `social` matches no send task.
@@ -0,0 +1,64 @@
1
+ # The mailbox test email (`mailbox.confirm`)
2
+
3
+ Audienti never signs in to your user's mailbox. You send from it through your own email
4
+ connector (Gmail or Outlook, through Composio, a Claude connector or similar), so you also
5
+ prove it works. A mailbox counts for campaigns only once you sent a test email from it to
6
+ itself, read it back and reported the code. Your user does nothing in this check: never ask
7
+ them for the code, and never send them to Settings to confirm a mailbox.
8
+
9
+ When your user connects a mailbox you reported, or asks for another test email in Settings →
10
+ Sending, you get one `mailbox.confirm` task for it. Its `detail.test` gives the mailbox as
11
+ you reported it (`mailbox`), its address as Audienti has it (`address`, or `null` when the
12
+ mailbox was reported under a name or an account id that is no address), the code (`code`) and
13
+ the subject to use (`subject`, `Audienti mailbox check` and the code).
14
+
15
+ 1. Claim the task: `audienti queue claim TASK_ID`.
16
+ 2. Send one email from that mailbox to its own address, and to nobody else. Use the address
17
+ your connector reports for the mailbox; `address` is only a hint, and if your connector
18
+ reports another, use that one. Use the subject the task gives and put the code in the body
19
+ too. Send nothing else.
20
+ 3. Read that mailbox's inbox with your connector until the email arrives: about once a
21
+ minute, for up to 15 minutes. Read only the test email. Note its message id and the time
22
+ it arrived.
23
+ 4. Report what you read, with the address you sent it to (`sentTo`):
24
+
25
+ ```json
26
+ { "results": [{ "kind": "mailbox.received", "code": "CODE", "sentTo": "ADDRESS_YOU_SENT_TO", "message": "MESSAGE_ID", "receivedAt": "2026-11-04T12:01:00Z" }] }
27
+ ```
28
+
29
+ ```sh
30
+ audienti outcomes report TASK_ID --file RESULTS.json
31
+ ```
32
+
33
+ 5. Delete both copies of the test email, the sent one and the received one, then report it:
34
+ `{ "results": [{ "kind": "mailbox.deleted", "message": "MESSAGE_ID" }] }`.
35
+ 6. Report the task done: `audienti queue done TASK_ID --note "test email confirmed"`.
36
+
37
+ Use the code exactly as you read it back from the email; the dash and case do not matter.
38
+ Only the task's own code confirms the mailbox. A code of an older test email is refused
39
+ (`stale_code`), any other code too (`wrong_code`), and so is an email that arrived before
40
+ the task asked for it (`received_before_asked`), and so is an email sent to another of your
41
+ user's mailboxes (`other_mailbox`). Reporting the same email again returns
42
+ `already_recorded`.
43
+
44
+ You can do this check even if you can only read the workspace: your own test email is the
45
+ one result a read-only agent may report.
46
+
47
+ ## When you cannot send or read
48
+
49
+ Report which step failed, with a short reason, then report the task done:
50
+
51
+ ```json
52
+ { "results": [{ "kind": "mailbox.failed", "step": "send", "reason": "No Gmail connector" }] }
53
+ ```
54
+
55
+ - `send`: you have no connector that can send from the mailbox, or the send failed. Tell
56
+ your user exactly: "I need a Gmail or Outlook connector to send from your mailbox."
57
+ - `read`: the email went out but you cannot read the mailbox's inbox, or it did not arrive
58
+ in 15 minutes. Tell your user exactly: "I need a Gmail or Outlook connector that can read
59
+ your mailbox's inbox."
60
+
61
+ Settings → Sending and Capabilities then show your user which step failed. No new task comes
62
+ until they fix your connector and choose Send another test email. The task cannot be
63
+ reported done until you reported `mailbox.received` or `mailbox.failed`
64
+ (`mailbox_not_reported`).
@@ -0,0 +1,89 @@
1
+ # Outcome checks
2
+
3
+ An outcome check is a queue task (`outcomes.check`) that asks you whether the people
4
+ you invited on one of the user's LinkedIn accounts have accepted. Audienti schedules it
5
+ for you at the cadence you reported for that LinkedIn account (`checkEverySeconds`, every 6 hours
6
+ when you report none), and only while someone on that LinkedIn account waits for an accept. Each
7
+ task lists at most 100 people, so a LinkedIn account with more people waiting gets several tasks
8
+ in the same interval, and every waiting person is on one of them. You never decide when to check, and you never check
9
+ anyone the task does not list.
10
+
11
+ ## What the task gives you
12
+
13
+ The task's `detail` holds:
14
+
15
+ - `seat`: the LinkedIn account to check (`account` is the LinkedIn account you reported, for example
16
+ `linkedin.com/in/owner`).
17
+ - `waiting`: the people to check, at most 100. The list stays as it is while you hold
18
+ the task, so you can report on everyone it lists; no other check lists them meanwhile,
19
+ even one for the next interval. Each has `person` (the Audienti person
20
+ id you report against), `name`, `profileUrl` and `invitedAt` (when the invitation was
21
+ sent).
22
+
23
+ The task requires `profile.invitations.read` on that LinkedIn account. If you hold it, the task is
24
+ assigned to you.
25
+
26
+ ## The procedure
27
+
28
+ 1. Claim the task: `audienti queue claim TASK_ID`.
29
+ 2. Read the LinkedIn account's own sent invitations with your tool, or read each listed person's
30
+ profile to see the connection degree. Look up only the people in `waiting`. Do not
31
+ list, search or open anyone else, and do not page through the user's whole network.
32
+ 3. Keep to the limits you reported for this LinkedIn account. Spread reads out if your tool asks
33
+ you to. If you cannot check now (signed out, rate limited), release the task with a
34
+ note: `audienti queue release TASK_ID --note "why"`.
35
+ 4. Write what you found to a file and report it:
36
+
37
+ ```json
38
+ {
39
+ "results": [
40
+ { "kind": "linkedin.accept", "person": "PERSON_ID", "at": "2026-11-01T09:30:00Z" },
41
+ { "kind": "linkedin.invite_closed", "person": "PERSON_ID", "reason": "withdrawn" },
42
+ { "kind": "linkedin.profile_degree", "person": "PERSON_ID", "degree": 2 }
43
+ ]
44
+ }
45
+ ```
46
+
47
+ ```sh
48
+ audienti outcomes report TASK_ID --file RESULTS.json
49
+ ```
50
+
51
+ 5. Report the task done: `audienti queue done TASK_ID --note "3 checked, 1 accepted"`.
52
+
53
+ ## Result kinds
54
+
55
+ - `linkedin.accept {person, at}`: the person accepted. `at` is when they accepted, as
56
+ close as your tool tells you. Audienti ends the person's wait and plans the next step
57
+ of the playbook, which appears in your queue.
58
+ - `linkedin.invite_closed {person, reason}`: the invitation is no longer pending.
59
+ `reason` is `withdrawn` (the user or you withdrew it) or `expired` (LinkedIn closed
60
+ it). Audienti records the fact; the wait still ends at its timeout.
61
+ - `linkedin.profile_degree {person, degree}`: optional evidence, the connection degree
62
+ you read on the profile (1, 2 or 3). A 1 means connected; report it as an accept as
63
+ well when you know the time.
64
+
65
+ Leave out anyone still pending. They stay on the next check until they accept or the
66
+ wait times out.
67
+
68
+ ## What the report returns
69
+
70
+ `results` has one entry per result you sent, in the same order. Each person is recorded
71
+ on their own, so one person who cannot be recorded does not hold back the others.
72
+
73
+ - `status`: `recorded` (written now), `already_recorded` (an earlier report wrote it;
74
+ `eventId` is that earlier event) or `not_recorded` (nothing was written).
75
+ - `reason`: why, when there is one. `participation_ended` or
76
+ `participation_unavailable` means the fact was recorded but nobody moves, because
77
+ the person's campaign participation ended or its playbook cannot be read.
78
+ `not_found` with `not_recorded` means the person is no longer in the workspace.
79
+ - `next`: the person's next step after the report, when there is one.
80
+ - `failed`: the accept was recorded, but planning the next step failed. Audienti shows
81
+ the failure on the person's timeline; `reason` holds its code. Sending the same
82
+ accept again returns the same failure until it is resolved.
83
+
84
+ ## Errors
85
+
86
+ - `not_claimed`: claim the task first, or your lease expired. Read the queue again.
87
+ - `not_on_list`: you reported a person the task does not list. Report only the listed
88
+ people.
89
+ - `validation_error`: a result has a wrong kind, person id or time.