Agent policy and buyer aggregation
Outcome. You can distinguish the two policy surfaces a buyer controls, understand their authority boundaries, and locate the pinned extension points: the per-round negotiation chain and the cross-candidate aggregation policy.
One policy chain per side
Each negotiation side runs one ordered middleware chain per round. Every middleware has the same shape — take the round history and context, then either defer to the next policy or short-circuit with a decision — and the last entry must always decide. Accept, Reject, Propose, and Timeout are outcomes of this single pluggable chain, not separate mechanisms:
# Maybe<Response> * Context
# None -> defer to the next policy with the (possibly updated) ctx
# Some<Response> -> short-circuit the chain; that response is sent
NegotiationStep = tuple[Optional[NegotiationDecision], NegotiationContext]
The context carries the listing, the escrow proposal, the side's reference
price and direction, and a free-form intermediate slot policies use to
publish computed state downstream. Source:
the middleware module
and the synchronized contract in
Configuration.
Custom policies register two ways, without forking anything: an in-process
decorator (@register_negotiation_middleware("region_lock")), or file
discovery from $XDG_CONFIG_HOME/arkhai/policies/<name>/policy.py
(the folder name becomes the policy name).
Aggregation: order, score, race, or select
Across the candidates discovery returned, the buyer runs exactly one
aggregation policy. It owns the iteration shape — sequential or parallel,
take-first-agreed or compare-all — receives a per-listing negotiate
callback, and returns the winning (listing, outcome) pair or None:
| Policy | Iteration | Winner |
|---|---|---|
best_price | Parallel across all candidates | Lowest agreed amount |
cheapest_first | Sequential, ascending advertised price | First that agrees |
registry_order (core default) | Sequential, response order | First that agrees |
random_shuffle | Sequential, uniform shuffle | First that agrees |
priceless_last | Priced (cheapest first), then priceless | First that agrees |
fastest_agreed | Parallel race | First agreement; others cancelled |
Select with [aggregation] policy or per-invocation
--aggregate-by <name>; bound fan-out upstream with max_matches_to_try;
cap best_price wall-clock with best_price_timeout. Custom aggregation
policies register exactly like negotiation policies, from
aggregation_policies/ directories.
What aggregation can never do
Aggregation orders, scores, races, or selects. It cannot change seller
terms, and it cannot take over commitment: under buy, the user approves
the aggregation policy's pick (interactively unless --yes), and the
explicit agreement remains the buyer's. This is the buyer-side composition
hook the SCM design notes describe — coordination composes around the
signed primitives; it does not move inside them.
External agents
A long-running agent is one more caller of the same signed role interfaces, executing the same configured policies under one participant's authority. Nothing in the pinned source gives an agent independent market authority, and SCM ships no agent runtime — only the protocols and hooks above.
Next steps
- The synchronized policy catalog: Configuration.
- Resume semantics for interrupted runs — the run log records the policy
and parameters, and
--from <run_id>rebuilds the chain under the recorded policy: Buyer quickstart.