Skip to main content

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:

PolicyIterationWinner
best_priceParallel across all candidatesLowest agreed amount
cheapest_firstSequential, ascending advertised priceFirst that agrees
registry_order (core default)Sequential, response orderFirst that agrees
random_shuffleSequential, uniform shuffleFirst that agrees
priceless_lastPriced (cheapest first), then pricelessFirst that agrees
fastest_agreedParallel raceFirst 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.