# Agents guide for bizhunters.io BizHunter is a business-acquisition workspace. The application behind the sign-in wall is private; automated agents should treat these public resources as the entire machine-readable surface of this site. ## What agents may do - Read the homepage, llms.txt, robots.txt, sitemap.xml, and this file. - Use the device-login page (https://bizhunters.io/device) only as part of an explicit human-approved CLI login: a human confirms the terminal code. - Follow links in llms.txt to the CLI documentation and package registry. - After a human approves a scoped CLI credential, configure the workspace as described in Initial setup. Defining a DealBox is optional. ## What agents must not do - Do not attempt to reach application routes (/deals, /deal-boxes, /settings, /tasks, /businesses, /buyers, /companies): they require an authenticated session and are outside the public surface. - Do not automate sign-in, cookies, or session tokens. - Do not submit forms or create records from automated web browsing. The supported automation surface is the BizHunters CLI with a human-approved, scoped credential; extraction agents report findings for human review and do not choose pursue, watch, or pass on their own. An explicit human instruction to run that command is the decision. The agent runs it when its key allows the command. ## Initial setup Do this when the human asks an agent to prepare the workspace, or when a task needs preferences that are not stored yet. Read what is already stored before writing anything. An agent can read, record an opportunity, or underwrite without a DealBox. Create one when the human wants a distinct search thesis. 1. Install the BizHunters CLI from the registry linked in llms.txt. 2. Ask the human to approve a device login at https://bizhunters.io/device. Request only the scopes the task needs. read inspects. dealboxes:write saves the company profile and DealBoxes. opportunities:write records an opportunity. sourcing:write queues a discovery run. bizhunters describe lists the scope for every other command. 3. Before a write, run bizhunters describe, bizhunters schema show TYPE, and the command with --dry-run. Use those contracts. Do not invent fields. 4. Read bizhunters profile get and bizhunters dealboxes list. For a box you might use, read bizhunters dealboxes thesis BOX_ID. 5. Save a company profile when the human is setting preferences and the stored profile does not already state them: bizhunters profile save --file profile.json, body AcquisitionProfile. Record only what the human stated. Leave numerical criteria unset when they did not give a threshold. Current income is a comparison benchmark unless they explicitly make it a requirement. Unset stays unknown. Explicit zero is a real value. 6. Create a DealBox when the human wants a separate search: bizhunters dealboxes create --file box.json, body CreateDealBoxRequest (name, optional 7-character color such as #047857, optional qualification_criteria of at most 8000 characters). Then bizhunters dealboxes save-thesis BOX_ID --file thesis.json, body DealBoxThesis: geography, industries, capabilities, project interests, thesis text, and any owner-role, income-strategy, approval-gate, or threshold overrides. Empty overrides inherit the company profile. A missing threshold is not a pass. 7. Queue sourcing only against a DealBox whose thesis the human accepts: bizhunters sourcing run BOX_ID --file run.json, body StartSourcingRequest (instructions and maximum_candidates). Extraction and underwriting come after this, and only when that is the task. Underwriting reads the stored profile and thesis. It does not keep a second copy of those preferences. An inbound listing follows Inbound listings below. ## Inbound listings A Gmail message or other listing that arrives on its own is not a search. This section is the whole extraction procedure. An agent that can read only this site already has every step. Do not look for a private skill or repository file. 1. Install the CLI with the pip command in llms.txt when bizhunters is not already installed. A notice on stderr that a newer release is published is not an error. The JSON result stays on stdout. Reinstall with that same pip command. The CLI does not upgrade itself. 2. Run bizhunters auth whoami. This import needs read and opportunities:write. If the key lacks opportunities:write, stop and ask the human to approve bizhunters auth login --scope read --scope opportunities:write at https://bizhunters.io/device. Do not request dealboxes:write for this task. 3. Before the write, run bizhunters describe, bizhunters schema show ListingIntakeRequest, and bizhunters opportunities import --dry-run --file intake.json. Use those contracts. Do not invent fields. 4. An email that says businesses are for sale is not a listing. Do not submit that email, its subject, or a truncated name as listing_text. Open each listing URL in the message. Sign in when the site requires it. Extract the listing page itself. If you cannot sign in, stop and ask the human. Do not invent the listing. Do not use a https://bizhunters.io/intake/gmail/ URL as the listing source_url. If the message has no listing URL, stop and ask the human. Do not create an opportunity from the email. 5. Run bizhunters opportunities import --file intake.json with ListingIntakeRequest. name is 1 to 200 characters, taken from the page. source_url is the listing page's HTTPS URL. listing_text is the page text, at least 20 characters. Omit deal_box_id. Do not ask which DealBox to use. Do not assign a box. Do not set asking_price or cash_flow. Research does not open the listing site. Creating a DealBox remains the separate setup task above. The returned id is that record's id. New records use UUID version 7. Older ids stay valid. One business can have several records. The record to keep working has canonical true. Another record of the same business points at it with canonical_opportunity_id. The names do not have to match. A shared BizBuySell, BizQuest, or LoopNet listing number does not attach a page by itself. A different URL creates a new record unless opportunity_id is set to the record that should receive this page. The same source_url updates that same record and does not assign a DealBox. A longer name that starts with the stored name replaces it, up to 200 characters. The same text and name do not queue another job. Before creating another record, run bizhunters opportunities list --query search=NAME and read the records. Set opportunity_id only when this page is that same business. Omit opportunity_id when it is new or when you are not sure. Do not guess from the industry or from a similar name. Send the page's own text. 6. Report the opportunity id and every missing or ambiguous field. Do not run diligence. Do not choose pursue, watch, or pass on your own. When the human explicitly directs one, follow When a listing page changes. Do not queue sourcing. A Gmail keyword filter is a separate trigger, not part of this import. When the listing page later shows pending, sold, or gone, follow When a listing page changes. Do not archive the record to store that fact. ## Clean up a thin import A record whose text is only an email subject, whose source_url starts with https://bizhunters.io/intake/gmail/, or whose name was cut short is not a listing. Clean it with opportunities:write. Reinstall the CLI with the pip command in llms.txt when bizhunters describe does not list opportunities field-rules, link, collapse, archive, and reeval. A notice on stderr is not an error. The CLI does not upgrade itself. 1. Run bizhunters opportunities field-rules. Saving a different description stores that text where research can read it and queues listing_intake. Changing name, source, broker, address, phone_number, email, buyer, business, or DealBox does not. Collapse does not copy asking_price or cash_flow. 2. List working records with bizhunters opportunities list. Archived records are omitted. Add --query include_archived=true to see them. Add --query canonical=true to see only canonical records, or canonical=false to see records that point at one. 3. Choose the record to keep. Link each other record of the same business: bizhunters opportunities link CANONICAL_ID --file link.json with {"opportunity_id":"OTHER_ID"}. Names do not have to match. Link does not copy text and does not queue research. If the command says to link the canonical record, use the record whose canonical field is true. 4. Copy fields and archive the other record with bizhunters opportunities collapse CANONICAL_ID --file collapse.json, for example {"opportunity_id":"OTHER_ID","fields":["description"]}. Allowed fields are name, description, source, broker, address, phone_number, and email. Asking price and cash flow are rejected. Copying a different description stores that text for research and queues listing_intake. Copying only a phone number does not. Add "force_reeval":true when you still want fresh documents. 5. Archive a stub you will not keep: bizhunters opportunities archive ID --file archive.json with {"archived":true}. The pipeline status stays as it is. Return it with {"archived":false}. Archiving a record that still has linked records in the working list is rejected. Link or archive those first. 6. Put the listing page on the canonical record. Import with opportunity_id set to that id, source_url set to the real page, and listing_text set to the extracted page. Do not import the email as the listing. 7. A typo in a field that does not queue research stays a typo until you ask for fresh documents. bizhunters opportunities update ID --file patch.json can include "force_reeval":true. bizhunters opportunities reeval ID queues listing_intake without changing fields. A second call does nothing while one job is already queued. Do not use research run for this. That command needs research:write. If a listing_intake job fails, report its id and the failure code, then stop. Do not retry it and do not ask for research:write. A human owner retries a failed job. ## When a listing page changes A pending page, a sold page, or a missing page is a fact about the listing. The opportunity status is the acquisition milestone. Archive hides a record and leaves that milestone unchanged. Do not archive a listing to record that the page changed. Importing the same source_url again attaches the new page text and does not change the milestone. opportunities reeval queues intake and does not change it either. Pending, or under contract: leave the milestone where it is unless the human explicitly says to mark that opportunity lost. An agent that only notices the page reports the opportunity id and what the page said, then stops. Sold, or the page returns 404: an agent that only notices the page reports the opportunity id and the page fact, then stops. It does not archive the record and it does not decide the pass. When the human explicitly says to mark that opportunity lost, an agent whose key has workflow:write runs bizhunters workflow advance DEAL_UUID --file with {"command":"mark_lost","notes":"Listing sold"} or {"command":"mark_lost","notes":"Listing page returned 404"}. That writes closed_lost. Put the human's reason in notes when they gave one. A company key cannot hold workflow:write, so that key reports and stops even when told to mark it lost. Price and earnings stay unknown until a human accepts a financial proposal. Open the existing opportunity to change its DealBox. Importing does not move a listing onto a different opportunity or into a different DealBox. Fit is a later deal_box_assignment proposal from research: a box id, a fit summary, disqualifiers, and evidence, after comparing the lead with each real DealBox. The box is written only when a human accepts that proposal. Rejecting it leaves the lead unassigned. Sourcing is the other direction. bizhunters sourcing run starts from a DealBox whose thesis the human accepts. Those candidates stay pending until a human accepts one, which files it into that same box. The server checks that the box belongs to the company. It does not choose the fit.