dfx-intel (Python)
A small client for DFX Intelligence, an evidence-backed research service on private markets: private credit, private equity, venture, family offices, registered investment advisers, public pension allocators and US commercial real estate. It is written for agents. Every answer carries its claims, the URLs of the documents those claims come from, what is uncertain, and what lies outside coverage.
The core uses only the standard library (Python 3.9+). The framework adapters are optional extras.
pip install dfx-intel
Quick start
from dfx_intel import research
r = research("Which BDCs lend to Kaseya, and which of them are current lenders?")
print(r.state, r.answer_state) # OK OK
print(r.answer) # one or two sentences, built only from cited claims
for c in r.claims:
print(c.claim_id, c.predicate, c.claim_type) # VERIFIED_FACT, REPORTED_FACT, DERIVED, INFERENCE ...
for url in r.evidence_urls:
print(url) # the SEC filing, IAPD report or plan disclosure behind the claims
print(r.uncertainty, r.coverage_limits)
if r.has_more:
page2 = research(continuation=r.continuation)
Public, runnable examples (anonymous research, citations, errors, pagination, a member key from the environment and a short tutorial), each with the output of a live run, are at https://github.com/Capital-W-Holdings/us-property-parcel-real-estate-debt/tree/main/examples/sdk.
examples/ in the SDK source tree holds further scripts. Each one ran once (against production, except the two receipt readers, which run offline), and its real output sits next to it
(.out.txt, plus .out.json for the raw payload). The UTC time of the run is in .ran_at.
| example | investigation |
|---|---|
| 01_lenders_to_portfolio_company.py | Lenders: which BDCs lend to Kaseya (an Insight Partners company), then the next page through the continuation |
| 02_advisor_moves.py | People: which advisors joined Rockefeller Capital Management in the last 90 days, from IAPD |
| 03_allocator_commitments.py | Allocator: which funds CalPERS committed to in 2025, and for how much (see known issues below) |
| 04_verify_and_resolve.py | Verify "Blackstone Private Credit Fund lends to Auctane", then resolve "Thoma Bravo" to DFX ids |
| 05_not_covered.py | NAV per share of Ares Capital, which DFX does not read (the limit is stated in the answer) |
| 06_delegate_a2a.py | The same lender question delegated over A2A (message/send, then tasks/get) |
| 07_read_receipt.py | Read a research receipt (dfx.receipt/2): facts vs inferences, relationships, freshness, and the exact follow-up calls; runs offline on the bundled receipt (see ../RECEIPTS.md) |
| 08_receipt_as_documents.py | A receipt as retrieval documents (LangChain Documents when installed), marker and source in metadata |
| 09_sponsor_portfolio_credit.py | Sponsor and financing: which BDCs lend to Thoma Bravo portfolio companies, and how much they hold |
| 10_ownership_and_lenders.py | Ownership and lenders of one company (Hyland Software), and how to tell from plan.operations which half was answered |
| 11_fund_lps.py | Fund relationships: which public pension plans committed to Vista Equity Partners funds, and for how much |
| 12_langchain_tool.py | The LangChain StructuredTools, invoked as a ToolNode would (no model key needed) |
| 13_openai_agents_tool.py | The OpenAI Agents SDK function tools, invoked through on_invoke_tool (no model key needed) |
| 14_claude_agent_sdk.py | The Claude Agent SDK options (mcp_servers, allowed_tools); runs a query only with DFX_RUN_AGENT=1 |
Every example except 07 and 08 (which read the bundled receipt offline) runs anonymously against production with no paid API. The adapter examples run an agent only when you set a model key yourself.
Checked against the primary documents (2026-10-12)
Before these outputs were published, figures in them were read against the documents they cite:
| example | claim in the output | primary document | result |
|---|---|---|---|
| 01 | Blackstone Private Credit Fund holds Kaseya debt, principal $161.7M, fair value $98.5M at 2026-06-30 | BCRED 10-Q for 2026-06-30, schedule of investments (two Kaseya term loans: $36.7M at $22.0M and $125.0M at $76.4M, in thousands as printed) | matches |
| 02 | An advisor registered with Rockefeller Capital Management from 2026-09-30, after Truist Advisory Services (ended 2026-10-01) | That advisor's IAPD report (IA registered since 09/30/2026; Truist 04/18/2022 to 10/01/2026) | matches |
| 09, 10 | Hyland Software is a Thoma Bravo portfolio company | Golub Capital's 2023 release on Hyland's $3.4B unitranche ("a portfolio company of Thoma Bravo since 2007"). The URL DFX cites for it now answers 404; the release is at golubcapital.com/?p=2714 | matches, citation link dead |
| 09 | Thoma Bravo invested in Grayshift | Peak Equity, 2022-07-07: "Thoma Bravo ... announced the completion of its strategic investment in Grayshift, LLC" | matches |
| 10 | Blue Owl Technology Finance Corp. holds Hyland Software debt, principal $147.5M, fair value $142.9M at 2026-06-30 | ORTF 10-Q for 2026-06-30 (first lien loan $147,542K principal at $143,116K; undrawn revolver at $(215)K) | matches |
| 11 | Minnesota State Board of Investment committed $200M to Vista Equity Partners Perennial, vintage 2020 | MSBI private markets asset listing, as of 2026-06-30 | matches |
| 11 | New Jersey committed $300M to Vista Equity Partners Fund VII (2018), $200M to Vista Co-Invest Fund 2017-3, Fund VI and Fund IV | NJ Division of Investment Directors Report, period ending 2026-05-31 | matches |
Known issues seen in these outputs
They are left in the outputs, unedited, because the outputs are real. Each was reported to the DFX research team.
- An Auctane run (not shipped as an example) booked a joint venture's loan to its parent BDC: Blackstone Private Credit Fund's Auctane position was given as $889.4M at fair value, while BCRED's own schedule shows $832.0M and the other $57.3M is held by Emerald JV LP.
- 03: one fund name carries a row number from the source table ("29. Blackwell Capital Partners II"), and one evidence URL is a Census Bureau survey documentation page, not a CalPERS document.
- A two-part question can come back with only one part answered and no sentence saying so. Example 10 shows how to
read
raw["plan"]["operations"]to see which lookup returned NO_MATCH. verifyfor an anonymous caller returns SUPPORTED with every source URL LOCKED, so the caller cannot open the document. The amounts and dates still come back.
API
| call | transport | returns |
|---|---|---|
research(question, *, continuation=None, detail=None, depth=None, entity=None, workflow=None, as_of=None, since=None) |
MCP tools/call research on /mcp/core |
ResearchResult |
verify(claim, **structured) |
MCP tools/call verify |
VerifyResult: SUPPORTED, PARTIALLY_SUPPORTED, CONTRADICTED or UNKNOWN |
resolve(name, *, domain=None, entity_type=None) |
MCP tools/call resolve_name |
ResolveResult: DFX ids, with total_lower_bound meaning "at least N" |
delegate(question, *, arguments=None, context_id=None, poll_interval=2.0, max_wait=120.0) |
A2A 0.3 message/send on /a2a, then tasks/get until final |
DelegateResult: the task, with .result as a ResearchResult |
The module level functions share one Client(). To set options, build your own client:
from dfx_intel import Client
dfx = Client(agent_name="acme-research-agent", user_agent_suffix="acme/1.2")
Every result keeps the server's payload, unchanged, under .raw. Fields lifted onto ResearchResult:
stateandanswer_stateanswerandbasisclaimsevidence_urlsuncertaintyandcoverage_limitswithheldcontinuationandpagenext: follow-up calls with exact argumentsreceipt_url: passed through when the server provides one, None otherwiseinteraction_idretry_after_secondsanderror
States, not exceptions
NO_MATCH, NOT_COVERED, UNKNOWN, RATE_LIMITED and INVALID_ARGUMENT arrive as result.state. They do not
raise. Each tells the agent something:
NO_MATCH: the name resolved, but nothing matches. Near misses are in.raw["candidates"], and they are never the subject.UNKNOWN: the subject could not be resolved, or there is no evidence either way. Absence of evidence is never a contradiction.NOT_COVERED: DFX does not read that kind of record. An answer can also beOKand still list aNOT_COVEREDitem incoverage_limits.RATE_LIMITED:retry_after_secondssays when to try again.TEMPORARILY_UNAVAILABLE: the exchange is at capacity or not answering (HTTP 503). Nothing ran and nothing was charged;retry_after_secondsis set when the exchange sent one.API_KEY_INVALID,NOT_IN_PLAN,ENTITLEMENT_REQUIRED: the key is not recognised, or the plan does not cover the call..rawcarrieshow_to_fixor the plans URL.HTTP_ERROR: any other non-2xx answer;.errorcarries the server's description (for example the member door's 401 "sign in with your DFX account").
Only transport failures raise TransportError (DNS, TLS, an unreadable body, or no answer within timeout, 90 s by
default). An A2A endpoint that answers 404
gives a DelegateResult whose .raw["state"] is NOT_DEPLOYED.
Free and member access
Anonymous (free, no key, no sign-up). All the read tools work. Per DFX's published policy:
- A list names its first 5 rows in full and gives only a count of the rest (
withheld,locked). - A record names its subject and the first 3 related names per section.
- Contact values and decision-maker names are never returned.
- Public filer identity and dated public-record facts are served. Resolved relationships come as counts. Derived scores are not served.
Anonymous use is limited per network address, not per agent: 20 calls a minute and 300 an hour, and the search and
changes tools (research is one of them) share 60 an hour. Callers behind one office or cloud address share these
budgets (budget.identity_basis network_prefix in the RATE_LIMITED envelope). agent_name (X-Agent-Name) labels
your agent; it does not give it a separate budget. A refused call is not charged and carries retry_after_seconds.
Member. Set DFX_API_KEY to a dfx_live_ account key, or pass api_key=. The key goes out as
Authorization: Bearer <key>. It unlocks the rows withheld from anonymous callers on the verticals your plan
covers. A member is metered on the membership's own allowance in retrieval units a day and a month, not in calls:
one research call uses 6 to 18 units depending on its depth. Allowances by plan: https://dfxintel.com/data-factory/plans
Withheld is not missing. When an answer says rows are withheld, they exist, and the answer counts them.
Coverage limits (stated by the service itself)
- Private credit comes from BDCs' own schedules of investments in SEC filings. Banks, CLOs and private funds that file no schedule are invisible. Principal is the sum of BDC pieces, so it is a lower bound.
- Advisor moves use IAPD registration end and begin dates, not announcement dates. The book of business is not public.
- Allocator commitments are the plan's own printed disclosures. Deployed capital cannot be observed from them.
- Some rows cite only a dataset, or nothing. The answer says so (
uncertainty,citation_check), andevidence_urlsnever invents a URL for them.
Each answer lists its own limits in coverage_limits and do_not_conclude. Read them before you rely on a negative.
Framework adapters
| framework | status | how |
|---|---|---|
| LangChain / LangGraph | shipped, tested offline against langchain-core 1.6.9, run live 2026-10-12 (example 10) | pip install "dfx-intel[langchain]", then from dfx_intel.adapters.langchain import dfx_tools |
| OpenAI Agents SDK | shipped, tested offline against openai-agents 0.23.1, run live 2026-10-12 (example 11) | pip install "dfx-intel[openai-agents]", then from dfx_intel.adapters.openai_agents import dfx_tools |
| Claude Agent SDK | config helper only (DFX is a remote MCP server) | from dfx_intel.adapters.claude_agent_sdk import mcp_servers, allowed_tools |
| CrewAI | snippet only | below |
Each adapter wraps the same two plain functions (dfx_research(question, continuation="") and dfx_verify(claim)).
Both return JSON that keeps the state, claims, evidence URLs, uncertainty and continuation.
Claude Agent SDK:
from claude_agent_sdk import query, ClaudeAgentOptions
from dfx_intel.adapters.claude_agent_sdk import mcp_servers, allowed_tools
options = ClaudeAgentOptions(mcp_servers=mcp_servers(), allowed_tools=allowed_tools())
CrewAI. This is a snippet, not an adapter. CrewAI could not be installed in the environment where this package was built (Python 3.14; its tiktoken dependency failed to build), so no tested adapter ships. CrewAI agents can use DFX as a remote MCP server directly:
from crewai import Agent
analyst = Agent(role="Private credit analyst", goal="Answer with cited sources", backstory="...",
mcps=["https://exchange-production-9123.up.railway.app/mcp/core?c=crewai"])
Endpoints and attribution
- MCP:
https://exchange-production-9123.up.railway.app/mcp/core?c=sdk-py. A2A:/a2aon the same host. Override the host withDFX_BASE_URLorbase_url=. - Every request sends
User-Agent: dfx-intel-python/<version>. - Anonymous URLs carry
?c=sdk-pyso DFX can count calls made through this package. LangChain tools use?c=langchain. Passchannel=Noneto send no tag. - A member request is never tagged: a request with a key, or a URL ending in
/member, goes out bare. - A2A tasks are held in the serving process's memory for up to 24 hours. A restart drops them, and
tasks/getthen answers task not found.
Tests
python3 -m pytest -q sdk/python/tests
The tests are offline. They run against recorded production responses in sdk/fixtures, and each fixture names
where it came from.
Licence
MIT. See LICENSE.
Metadata
Release files for dfx-intel 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dfx_intel-0.1.1.tar.gz | 34.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dfx_intel-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.9 kB
Release files / dfx_intel-0.1.1.tar.gz
| Download URL | dfx_intel-0.1.1.tar.gz |
|---|---|
| Size | 34.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9827536fe12d4c866c2f55483c2add0ae65deb32fa1df8694f884a8c3924c81a
|
|
BLAKE2b-256 checksum How to use checksums |
39d7958799a545e9104ec6866b39c54f32c53423c73f321aa728485a958ab0cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / dfx_intel-0.1.1-py3-none-any.whl
| Download URL | dfx_intel-0.1.1-py3-none-any.whl |
|---|---|
| Size | 23.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
57a81824cdad91e8cbf539750b7dd233ddb57fb50125b81e3967de84fd7a88d8
|
|
BLAKE2b-256 checksum How to use checksums |
fd18637a3cd2f22663fdda7c7b095ba5933b3b895fc2322a6879e311030f18df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Security for dfx-intel 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.