Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- SDK: `client.prospecting.update_plan(run_id, ProspectingPlanSettings(plan_version=..., ...))` (sync and async) changes a proposed plan's contact engine, company-check engine, search provider (`"none"` skips web research) and `max_spend_usd` (a USD spending limit on records and per-request fees at the plan's rates; `0` removes it) before approval, then returns the run with the re-estimated plan at a new `plan_version`. Prospecting `stop_reason` gains `candidate_limit` (candidate cap reached) and `credit_limit` (stopped at the spending limit, results kept); `candidates_exhausted` now means only that the search ran dry. `stop_reason` stays an open string. Runs without a chosen contact engine research on DiscoLike Groove, or find indexed contacts only with no search provider.
- SDK: `client.prospecting.answer_intake(run_id, answers, idempotency_key=..., summary=None)` (sync and async) answers the guided-intake card a run asks before it drafts a plan. `answers` maps a question key (`company_activity`, `industry`, `geography`, `company_size`, `persona_roles`, `list_size`) to `IntakeAnswer(values=[...], other="...")`; the card's fields are on the `kind="question"` message's `data`. `ProspectingMessageRequest.text` is now optional, so a message can carry `intake` alone; a message with neither `text` nor any `intake` answer is rejected locally, as the API rejects it.
- SDK: `ProspectingInFlight` gains `progress` (0-100 or `None`) for the running item.
- SDK/CLI: prospecting run shape and limits. `ProspectingPlanSettings` gains `target_companies`, `contacts_per_company`, `max_provider_spend_usd` (USD on your own AI and search provider keys; `0` removes it), `deliverable` (`"leads"`, or `"accounts"` for the checked companies only) and `goal` (`"leads"`, or `"companies"` to work only `target_companies` matching companies), all optional. `ProspectingBrief` takes `deliverable` and `goal` too, sent only when set, and the CLI's `prospecting start` gains `--deliverable` and `--goal`. `update_plan()` rewrites the latest plan message in place under the same `seq`, so read the plan from the run it returns. `ProspectingRun` gains `pipeline_phase` (`"companies"`, `"people"` or `None`) and `provider_cost_usd`, and its `brief` reports `deliverable` and `goal`. `CHECKPOINT_STOP_REASONS` gains `top_up`: a run asks before another round of companies (replies `continue`, `raise`, `shrink`, `finish`), and an auto run asks too when that round would pass a spending limit. `stop_reason` gains `provider_limit` and `companies_worked`. A saved contact list now carries every fit company as a row with its fit columns (`ICP Fit`, `Confidence`, `Reasoning`, `Segment`, `Customer segment`); a company with no contact has `contacts: []`, and an accounts run saves company rows only. Contacts per company go up to 10, and intake answers accept the `deliverable` key.
- SDK/CLI: prospecting run shape and limits. `ProspectingPlanSettings` gains `target_companies`, `contacts_per_company`, `max_provider_spend_usd` (USD on your own AI and search provider keys; `0` removes it), `deliverable` (`"leads"`, or `"accounts"` for the checked companies only) and `goal` (`"leads"`, or `"companies"` to work only `target_companies` matching companies), all optional. `ProspectingBrief` takes `deliverable` and `goal` too, sent only when set, and the CLI's `prospecting start` gains `--deliverable` and `--goal`. `update_plan()` rewrites the latest plan message in place under the same `seq`, so read the plan from the run it returns. `ProspectingRun` gains `pipeline_phase` (`"companies"`, `"people"` or `None`) and `provider_cost_usd`, and its `brief` reports `deliverable` and `goal`. `CHECKPOINT_STOP_REASONS` gains `top_up`: a run asks before another round of companies (replies `continue`, `raise`, `shrink`, `finish`), and an auto run asks too when that round would pass a spending limit. `stop_reason` gains `provider_limit` and `companies_worked`. A saved contact list now carries every fit company as a row with its fit columns (`ICP Fit`, `Confidence`, `Reasoning`, `Segment`, `Customer segment`); a company with no contact has `contacts: []`, and an accounts run saves company rows only. Contacts per company go up to 10 and default to 3 when the brief gives no count, and intake answers accept the `deliverable` key.

## 0.4.1 (2026-09-23)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Top-up rounds: when the companies checked so far yield too few contacts for the

Intake: a run can pause on a `kind="question"` message whose `data` lists a few option fields (keys `company_activity`, `industry`, `geography`, `company_size`, `persona_roles`, `list_size`). Answer it with `client.prospecting.answer_intake(run.run_id, {"company_activity": IntakeAnswer(values=["sell"]), "geography": IntakeAnswer(other="Ohio")}, idempotency_key=...)`: `values` are the option values you picked and `other` is free text. `IntakeAnswer` comes from `discolike.requests`. Invalid answers return 422; an answer sent after the card was superseded returns 409. Typing free text with `message()` instead still works.

Initial planning extracts company counts and contacts per company from the brief. Omitted settings keep that inference available, falling back to 1,000 companies and 1 contact per company. Explicit settings, including explicit defaults, override the text. Targets support 1–10,000 companies and 1–10 contacts per company. Candidate and action caps default to automatic (`0`); explicit maxima are 100,000 candidates and 10,000 actions. Result pages support up to 500 rows; recent-run lists support up to 50. Approved runs expose a stable `saved_query_id` for saved results.
Initial planning extracts company counts and contacts per company from the brief. Omitted settings keep that inference available, falling back to 1,000 companies and 3 contacts per company. Explicit settings, including explicit defaults, override the text. Targets support 1–10,000 companies and 1–10 contacts per company. Candidate and action caps default to automatic (`0`); explicit maxima are 100,000 candidates and 10,000 actions. Result pages support up to 500 rows; recent-run lists support up to 50. Approved runs expose a stable `saved_query_id` for saved results.

Run controls: `update_plan(run_id, ProspectingPlanSettings(plan_version=..., ...))` changes a proposed plan before approval. `contact_integration_id`, `validation_integration_id` and `search_provider_id` take an option id from the plan message's `contact_engine`, `company_check_engine` and `search_provider` (`data.contact_engine.options` and so on); omitted fields keep their choice, `search_provider_id="none"` skips web research, and `max_spend_usd` sets the most the run may spend on DiscoLike records and per-request fees, in USD at your plan's rates (`0` removes the limit; a plan with no per-record price is a 422). `max_provider_spend_usd` caps what the run may spend on your own AI and search provider keys, in USD (`0` removes it); once reached the run starts no new work and batches already running finish, so the total can end slightly above it. `target_companies` and `contacts_per_company` resize the plan, and its open limits are re-derived around them. `deliverable` is `"leads"` (find people at the companies) or `"accounts"` (return the checked companies only); `goal` is `"leads"` (keep adding rounds until the contact target is met) or `"companies"` (take `target_companies` matching companies and work only those). The same `deliverable` and `goal` are optional on `ProspectingBrief` at start, both defaulting to `"leads"` server-side, and the run's `brief` reports them. The plan is re-estimated at a new `plan_version`, and that is the version to approve; the latest plan message is rewritten in place under the same `seq`, so read it from the returned run's `messages` rather than past a `messages_after` cursor. An id outside the plan's options is a 422; a run that is not awaiting approval, a stale `plan_version`, or a run that cannot switch to `"accounts"` is a 409. With no engine chosen, contact research runs on DiscoLike Groove when you have a search provider and finds indexed contacts only when you have none. New `stop_reason` values, all plain strings: `candidate_limit` (the run reached its candidate cap; raise `max_candidates` or ask for fewer companies), `credit_limit` (the run stopped at its spending limit and kept its results), `provider_limit` (the run reached its AI provider spending limit), `companies_worked` (a `goal="companies"` run worked through the companies it was asked to take), and `candidates_exhausted` now means only that the search ran dry. A run reports `provider_cost_usd`, what it has spent so far on your AI and search provider keys as the providers report it (`None` when it recorded none; a custom AI endpoint reports no price), and `pipeline_phase`, `"companies"` while a phased run checks companies and `"people"` while it finds people at them (`None` for runs that interleave both).

Expand Down
2 changes: 1 addition & 1 deletion packages/discolike-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ discolike prospecting cancel RUN_ID

`start` and `approve` default to pausing at checkpoints, like the web chat: a pilot check on large lists (`pilot`), a search drifting off target (`tail_quality`), candidates running out short of the target (`short`), the target being met (`target_reached`), and another round of companies when the ones checked yield too few contacts (`top_up`). Pass `--auto` to never pause (except at a `top_up` round that would pass a spending limit); a poor pilot is then sharpened once and the run continues with a notice, stopping with `pilot_failed` only if the re-pilot fit is still under 20%. If sharpening itself fails, the run continues on the original criteria. On a terminal, `wait` shows the question, any sample companies and numbered replies at a checkpoint, sends your pick or your own text, and keeps waiting. Without a terminal, or with `--no-input`, it prints the run on stdout, a `needs_input` envelope (`message`, `stop_reason`, `suggested_replies`, `sample`) on stderr, and exits 7; answer with `prospecting message --text "<reply>"` and run `wait` again.

Omit `--target-companies` and `--contacts-per-company` to infer counts from the brief (fallback 1,000 and 1; up to 10 contacts per company). Explicit values override the text. `--deliverable accounts` returns the checked companies only, with no people; `--goal companies` takes `--target-companies` matching companies and works only those instead of adding rounds until the contact target is met. Both default to `leads`. `--max-candidates` and `--max-actions` are automatic when omitted or `0`; their maxima are 100,000 and 10,000. Targets allow up to 10,000 companies, status pages up to 500 rows, and lists up to 50 runs. Work caps do not cap provider charges.
Omit `--target-companies` and `--contacts-per-company` to infer counts from the brief (fallback 1,000 and 3; up to 10 contacts per company). Explicit values override the text. `--deliverable accounts` returns the checked companies only, with no people; `--goal companies` takes `--target-companies` matching companies and works only those instead of adding rounds until the contact target is met. Both default to `leads`. `--max-candidates` and `--max-actions` are automatic when omitted or `0`; their maxima are 100,000 and 10,000. Targets allow up to 10,000 companies, status pages up to 500 rows, and lists up to 50 runs. Work caps do not cap provider charges.

### Conventions

Expand Down
2 changes: 1 addition & 1 deletion packages/discolike-cli/src/discolike_cli/prospecting.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def start_command(
"--contacts-per-company",
min=1,
max=10,
help="Override contacts per company; otherwise inferred, default 1.",
help="Override contacts per company; otherwise inferred, default 3.",
),
max_candidates: int | None = typer.Option(
None, "--max-candidates", min=0, max=100000, help="Candidate work cap; omitted or 0 means automatic."
Expand Down
2 changes: 1 addition & 1 deletion packages/discolike/src/discolike/_generated/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3208,7 +3208,7 @@ class ProspectingBrief(DiscolikeRequest):
] = None
exclude_domains: Annotated[list[str] | None, Field(max_length=1000, title="Exclude Domains")] = None
target_companies: Annotated[int | None, Field(ge=1, le=10000, title="Target Companies")] = 1000
contacts_per_company: Annotated[int | None, Field(ge=1, le=10, title="Contacts Per Company")] = 1
contacts_per_company: Annotated[int | None, Field(ge=1, le=10, title="Contacts Per Company")] = 3
max_candidates: Annotated[int | None, Field(ge=0, le=100000, title="Max Candidates")] = 0
max_actions: Annotated[int | None, Field(ge=0, le=10000, title="Max Actions")] = 0
validation_integration_id: Annotated[str | None, Field(max_length=128, title="Validation Integration Id")] = None
Expand Down
2 changes: 1 addition & 1 deletion packages/discolike/tests/test_prospecting.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def test_request_defaults_preserve_explicit_quantity_intent() -> None:
explicit = ProspectingBrief(
brief=implicit.brief, target_companies=25, contacts_per_company=2, max_actions=0, max_candidates=0
)
assert (implicit.target_companies, implicit.contacts_per_company) == (1000, 1)
assert (implicit.target_companies, implicit.contacts_per_company) == (1000, 3)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Explicit default lacks coverage The updated test checks that an omitted count stays off the wire, but its explicit case uses 2. It does not protect the new default of 3 when a caller supplies it explicitly. That value must be sent so it can override a different count inferred from the brief; otherwise a future serialization change could break that behavior unnoticed.

Suggested change
assert (implicit.target_companies, implicit.contacts_per_company) == (1000, 3)
assert (implicit.target_companies, implicit.contacts_per_company) == (1000, 3)
assert ProspectingBrief(brief=implicit.brief, contacts_per_company=3).to_wire() == {
"brief": implicit.brief,
"contacts_per_company": 3,
}

Knowledge Base Used: Quality and delivery automation

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/discolike/tests/test_prospecting.py
Line: 420

Comment:
**Explicit default lacks coverage** The updated test checks that an omitted count stays off the wire, but its explicit case uses `2`. It does not protect the new default of `3` when a caller supplies it explicitly. That value must be sent so it can override a different count inferred from the brief; otherwise a future serialization change could break that behavior unnoticed.

```suggestion
    assert (implicit.target_companies, implicit.contacts_per_company) == (1000, 3)
    assert ProspectingBrief(brief=implicit.brief, contacts_per_company=3).to_wire() == {
        "brief": implicit.brief,
        "contacts_per_company": 3,
    }
```

**Knowledge Base Used:** [Quality and delivery automation](https://app.greptile.com/discolike/-/custom-context/knowledge-base/discolike/discolike-python/-/docs/quality-and-delivery.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

assert implicit.to_wire() == {"brief": implicit.brief}
assert explicit.to_wire() == {
"brief": implicit.brief,
Expand Down
Loading