You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GetProductsRequest.time_budget is {interval: int, unit: 'seconds'|'minutes'|'hours'|'days'|'campaign'}. The wire description: "the seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time." The response carries incomplete[] with per-scope estimated_wait so the buyer can decide whether to retry with a larger budget.
Every adopter that takes time_budget seriously needs to (1) install a deadline, (2) cancel in-flight work when the deadline expires, (3) project the unfinished scopes to the wire incomplete[] shape. None of that is business logic — it's protocol plumbing that's identical for every seller.
Salesagent: does not honor time_budget at all. Grep of src/core/tools/products.py shows zero references. The seller can run unbounded.
SDK: no deadline wrapper. MediaBuyHandler.get_products runs the adopter to completion regardless. Wire shape lives at src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1235-1244 (TimeBudget); response incomplete at src/adcp/types/generated_poc/bundled/media_buy/get_products_response.py:2786-2804 (IncompleteItem).
Proposed API
Framework wraps the adapter call in asyncio.wait_for, catches TimeoutError, projects whatever the adopter has produced so far via a checkpoint protocol:
_resolve_time_budget converts every wire unit (seconds/minutes/hours/days/campaign) to a deadline; 'campaign' → no deadline (matches wire description)
When deadline exhausted and adopter is plain (non-incremental), response has products: [], incomplete: [{scope: 'products', description: '...', estimated_wait: ...}]
When adopter implements IncrementalGetProducts, framework returns whatever batches landed before timeout, plus incomplete[] for the rest
estimated_wait populated from a hint protocol the adopter supplies (or omitted if no hint)
Test: time_budget={interval: 1, unit: 'seconds'} against a 10s adapter returns the timeout shape
Test: adopter that returns within budget passes through unchanged (no incomplete)
Test: time_budget absent → no deadline, no projection
time_budget is a request-shape parameter on more than just get_products — it also appears on request_proposal and likely others. Implement first against get_products; generalize when a second tool adopts the same wire shape.
Motivation
GetProductsRequest.time_budgetis{interval: int, unit: 'seconds'|'minutes'|'hours'|'days'|'campaign'}. The wire description: "the seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time." The response carriesincomplete[]with per-scopeestimated_waitso the buyer can decide whether to retry with a larger budget.Every adopter that takes
time_budgetseriously needs to (1) install a deadline, (2) cancel in-flight work when the deadline expires, (3) project the unfinished scopes to the wireincomplete[]shape. None of that is business logic — it's protocol plumbing that's identical for every seller.Parent tracker: #491.
Current state
Salesagent: does not honor
time_budgetat all. Grep ofsrc/core/tools/products.pyshows zero references. The seller can run unbounded.SDK: no deadline wrapper.
MediaBuyHandler.get_productsruns the adopter to completion regardless. Wire shape lives atsrc/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1235-1244(TimeBudget); responseincompleteatsrc/adcp/types/generated_poc/bundled/media_buy/get_products_response.py:2786-2804(IncompleteItem).Proposed API
Framework wraps the adapter call in
asyncio.wait_for, catchesTimeoutError, projects whatever the adopter has produced so far via a checkpoint protocol:Acceptance criteria
_resolve_time_budgetconverts every wire unit (seconds/minutes/hours/days/campaign) to a deadline;'campaign'→ no deadline (matches wire description)products: [],incomplete: [{scope: 'products', description: '...', estimated_wait: ...}]IncrementalGetProducts, framework returns whatever batches landed before timeout, plusincomplete[]for the restestimated_waitpopulated from a hint protocol the adopter supplies (or omitted if no hint)time_budget={interval: 1, unit: 'seconds'}against a 10s adapter returns the timeout shapeincomplete)time_budgetabsent → no deadline, no projectionincomplete[].scopeuses spec enum (products,pricing,forecast,proposals)Out of scope
refinewithaction='finalize'explicitly says buyer should NOT set time_budget)Cross-references
src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py:1235-1244,1599-1604src/adcp/types/generated_poc/bundled/media_buy/get_products_response.py:2786-2826src/adcp/decisioning/webhook_emit.pyOpen question (for the implementer, not blocking)
time_budgetis a request-shape parameter on more than justget_products— it also appears onrequest_proposaland likely others. Implement first againstget_products; generalize when a second tool adopts the same wire shape.