WEEK 07

An Agent Market: Reputation and Verification 에이전트 시장: 평판과 검증

In a market where no operator judges, whom do the agents come to trust? The shops and buyers from the week-06 assignment meet in one market and trade. The market keeps only the shop list, the orders, and the trade records; it does not rate which shop is good. That judgment comes from the memories and reviews the buyers leave. In the assignment you leave your shop and buyer in the always-on market.

01

Outline

PartContent
OPENTrades between week-06 submissions
SETUPJoining the market
R0Round 0: a market with no memory and no reviews
L1What a market needs to work
LAB 1A buyer that picks a shop and leaves a review
R1Round 1: memory and reviews
L2Reviews you can trust
LAB 2Signing receipts
R2Round 2: only signed receipts count
DiscussionWhat reputation misses
HWThe always-on market
MARKET: LECTURE AND LAB

An agent market: the rules change every round

GOAL: register your shop, trade through the market in three rounds, give your buyer memory and reviews, and sign receipts so that only real trades can be reviewed
OPEN

Trades between week-06 submissions

The optional part of the week-06 assignment was to trade with a classmate's shop built on a different harness. Two of the submitted shops were deployed on Cloudflare Workers. One student's buyer (Claude Code, haiku) traded three times per scenario with another student's deployed shop (Workers AI, Llama 3.3 70B according to its author, reserve enforced in code). The two students wrote their code separately.

ScenarioResultBuyer's offersShop's asks
lamp, budget 120, runs 1 and 3deal at 12090, 105, 115, 118, 119, 120150, 140, 130, 125, 122, 120
lamp, budget 120, run 2no deal90, 105, 115, 118, 120, 120150, 140, 130, 125, 122, 121
bookshelf, budget 230, 3 runsno dealup to 230down from 350 to 270

The card, SendMessage continued by taskId, the task states, and the receipt went back and forth with nothing to fix. In lamp run 2, which ended with no deal, both sides could accept 120, and the deal broke over a difference of 1. The buyer reached its budget of 120 with its fifth offer, and the shop asked 121. When the buyer sent 120 once more, that was its sixth offer, so the shop ended the deal. The buyer had no way to say that 120 was its last offer, and the shop's limit on the number of offers was neither on the card nor in any message.

The market in FIG. 01 extends such trades to the whole class.

buyers your buyer house buyers shops your shop house shops (some dishonest) 3 A2A trade MARKET (RUN BY THE INSTRUCTOR) order desk ledger review board registry item, budget, nonce receipts, hash chain reviews shops and cards dashboard: price per item, share per shop, reviews per shop 1 order 2 shop list 4 receipt 5 review 0 register
FIG. 01 The marketthe market keeps lists and records; buyers decide whom to trust

A shop registers its card URL with the registry (0). This is the job of FIPA's Directory Facilitator, seen in week 06. The market issues orders to buyers (1). An order holds an item, a budget, an expiry time, and a number used only once (nonce). Some orders have a budget below the week-06 reserve, so no shop can make a deal on them. A2A has no place to write delegation terms such as how much may be spent. In this market those terms go to the buyer as data, in the order. The buyer gets the shop list (2), picks one shop and bargains with it over A2A (3), sends the receipt to the ledger if there is a deal (4), and leaves a review (5).

The market blocks no trades, and the dashboard draws exactly what the ledger and the review board hold.

The market also has shops and buyers the instructor runs. They keep trades going when there are few participants, and some of them are dishonest. You are not told which.

The class follows the order of FIG. 02. Each round changes one market rule, and in the LABs between rounds you change your buyer and shop.

THE KIT RUNS YOU CHANGE WHAT COUNTS Round 0LAB 1Round 1LAB 2Round 2 buyer with a random shop market_buyer (buyer if missing) market_buyer (buyer if missing) nothing add market_buyer Ctrl+C, join again nothing sign receipts Ctrl+C, join again nothing every receipt no reviews every receipt anyone may review signed receipts behind an order
FIG. 02 The class, round by roundafter each LAB, stop the kit and join again
SETUP

Joining the market

The class kit market_kit.py handles everything exchanged with the market. Your shop and buyer need to know neither the market's URL nor its request format. As in the week-06 trade test, the shop and the buyer run with the commands in agent.json.

STEP 1Setup

The kit is in scripts/ of the course repository. On GitHub, press Sync fork on your fork, then get it with git pull. You need Python 3.10 or later. The python3 that comes with macOS is 3.9, so pip install ends with the error below. In that case install Python with brew install python@3.12 and make a venv with python3.12 -m venv .venv.

terminal
cd ai-agent-engineering-101          # your fork
git pull
ls scripts                           # market_kit.py and trade_test_week06.py should be listed
python3 --version                    # 3.10 or later
python3 -m venv .venv                # Windows: py -3.12 -m venv .venv
source .venv/bin/activate            # Windows: .venv\Scripts\activate
pip install "a2a-sdk[http-server]" uvicorn httpx cryptography
brew install cloudflared             # macOS
winget install --id Cloudflare.cloudflared   # Windows
cloudflared --version
the error on Python 3.9
ERROR: No matching distribution found for a2a-sdk[http-server]

STEP 2Make the week-07 directory

If your week-06 shop and buyer run, copy the week-06 directory. If you did not finish week 06, the kit's starter writes the week-06 reference shop and buyer for you. Do only one of the two.

terminal
# if your week-06 code runs
cp -r submissions/<student-id>/week-06 submissions/<student-id>/week-07

# if you did not finish week 06
python scripts/market_kit.py starter --dir submissions/<student-id>/week-07
starter output
starter written to submissions/<student-id>/week-07: seller.py, buyer.py, harness.py, agent.json
the shop does not sign receipts yet (LAB 2), and there is no market_buyer yet (LAB 1)
submissions/<student-id>/week-07/agent.json (starter)
{
  "harness": "starter: claude -p --model haiku (set HARNESS_CMD to use another harness)",
  "seller": {
    "cmd": ".../.venv/bin/python seller.py --port {port}"
  },
  "buyer": {
    "cmd": ".../.venv/bin/python buyer.py --url {url} --item {item} --budget {budget}"
  }
}

The starter never overwrites a file that already exists, and it writes the python path of the venv you ran it from. The starter's shop and buyer bargain with claude -p (haiku). To use another harness, put its command in the environment variable HARNESS_CMD. The starter shop does not sign receipts and has no market_buyer. You add market_buyer in LAB 1 and the signature in LAB 2.

STEP 3Trade once on your own

Before you join the market, run the week-06 trade test on the week-07 directory. This checks that your shop and buyer run from this directory and that the harness answers. The starter passed all 21.

terminal
python scripts/trade_test_week06.py submissions/<student-id>/week-07
end of the output (starter)
  PASS  buyer_holds_budget #3: deals [] (budget 230 < reserve 260)
  PASS  buyer_protocol_holds_budget #3: posts 6, without A2A-Version 1.0 0, follow-ups with taskId 4

21/21 passed -> PASS

A test that does not pass is printed as FAIL, and the trade records stay in logs/trade-test.log.

STEP 4Join the market

terminal
python scripts/market_kit.py join --dir submissions/<student-id>/week-07 \
    --market <market URL> --name <nickname> --tunnel
# Windows: drop the \ at the line ends and write it as one line

The market URL is announced in class. --tunnel gives your shop a public URL through cloudflared. The tunnel URL does not open right after it is printed. The shop is registered only once the card opens at that URL, so in the practice market log below it took 75 s from the [tunnel] line to the [market] line. For a shop you have already deployed, use --deployed <URL> instead of --tunnel.

This cloudflared tunnel opens without a Cloudflare account or login. If you cannot use cloudflared, follow the table below.

SituationWhat to do
It will not installUse an ssh tunnel. localhost.run in the commands below is a free tunnel over ssh with no sign-up, and its URLs end in lhr.life. ssh comes with macOS and Windows 11. Use two terminals as below
The tunnel does not openJoin with your buyer alone, using --buyer-only instead of --tunnel. Your shop is not in the market, but your buyer trades with other shops and leaves reviews. For the assignment, deploy your shop and register it with --deployed
terminal 2: ssh tunnel
ssh -R 80:localhost:8900 nokey@localhost.run
# in the output: ... tunneled with tls termination, https://72fcd1656a687c.lhr.life
terminal 1: kit
python scripts/market_kit.py join --dir submissions/<student-id>/week-07 \
    --market <market URL> --name <nickname> \
    --port 8900 --public-url https://<random>.lhr.life

--port 8900 makes the kit open your shop on the port ssh points to. The kit checks that the card opens at that URL, then registers it. When you open ssh again the URL changes, so run the kit's join again with the new URL.

YOUR LAPTOP week-07/ agent.json seller.py buyer.py harness.py shop_key.pem buyer_key.pem logs/market-kit.log grey: the kit writes these market_kit.py join your seller tunnel your buyer starts once, stays up gives it a public URL runs once per order market registry reads registers, gets orders, sends receipts
FIG. 03 What join startsonly the kit talks to the market
logs/market-kit.log (practice market, starter, excerpt)
11:34:16 [keys] added *.pem, .market/ to .gitignore
11:34:16 [keys] created shop_key.pem
11:34:16 [keys] created buyer_key.pem
11:34:20 [tunnel] https://graphics-computational-qualify-marion.trycloudflare.com, waiting until it answers
11:35:35 [shop] your seller on port 57055; others reach it through the kit at https://graphics-computational-qualify-marion.trycloudflare.com
11:35:35 [market] shop registered as walk-student, round 0
11:35:35 [market] buyer registered as walk-student, round 0
11:36:00 [order o-ca657cdf] lamp, budget 125, round 0
11:36:00 [kit] shop for this order: willow-goods http://127.0.0.1:56576
11:36:00 [buyer] -> 'I want to buy a lamp'  <- TASK_STATE_INPUT_REQUIRED | lamp: asking 130. Send your offer as a number.
11:36:05 [buyer] [harness] {"action": "offer", "price": 95}
11:36:05 [buyer] -> '95'  <- TASK_STATE_INPUT_REQUIRED | Counteroffer: 120.
11:36:08 [buyer] [harness] {"action": "offer", "price": 110}
11:36:08 [buyer] -> '110'  <- TASK_STATE_COMPLETED | Deal: lamp at 110.
11:36:08 [sign] receipt has no sig_seller
11:36:08 [market] ledger #57: lamp at 110, stars None, verified False (failed: sig_seller_ok, sig_buyer_ok), review not shown
[keys]Creates two keys and adds them to .gitignore. Never commit *.pem.
[market] registeredYour shop and buyer are registered with the market. Once these two lines appear, you are in the market.
[order], [kit]The order the market issued, and the shop the kit picked in Round 0.
[buyer]Your buyer's output, copied by the kit.
[seller]Other buyers bargaining with your shop. These lines appear in between whenever customers come.
verified FalseThe result of the checks used in Round 2. In Rounds 0 and 1, receipts without signatures count as deals too.

Keep the terminal running the kit open. When the kit stops, your shop and buyer stop with it.

The kit also replaces the interface address on your card with the public URL before serving it. A week-06 shop's card says http://127.0.0.1:<port>/, so served as it is, other people's buyers would send requests to their own computers.

STEP 5Find your shop on the dashboard

Open the market URL in a browser to see the dashboard. Find your nickname in the Share by shop table, and switch rounds with the R0, R1, R2 buttons. FIG. 04 is the Round 0 screen from the rehearsal.

The market dashboard in Round 0 of the rehearsal: counters for results, counted deals, walkaways and shops; a share-by-shop table with deals, receipts, receipts with an order, reviews and stars; a price-by-item table; and a share-over-time chart
FIG. 04 The dashboard, Round 0 of the rehearsalfind your nickname under Share by shop
Share, DealsThe number of deals counted in that round, and this shop's share of them.
ReceiptsThe number of receipts that reached the market in this shop's name.
With orderOf those, the number of receipts behind an order the market issued.
Reviews, StarsThe number of reviews shown and the average stars. Round 0 has no reviews.
Price by itemDeal prices per item. Spread is the gap between the highest and the lowest price.
LedgerAt the bottom of the dashboard, orders, results, and reviews are listed in the order they came in. Find your buyer's reviews here too.
R0

Round 0: a market with no memory and no reviews

In Round 0 the buyer is your week-06 assignment buyer, unchanged. The kit takes an order and runs the buyer command in agent.json with {url}, {item}, and {budget} filled in. The kit picks one of the registered shops at random and puts the kit's address that leads to that shop in {url}. The buyer remembers no past trades and has no reviews to read. Every receipt goes into the ledger, whoever sends it.

YOUR LAPTOP market order desk ledger market_kit.py picks a shop at random your buyer buyer.py from week 6 --url --item --budget a shop the kit's pick 12345 1 order: item, budget2 the kit runs buyer.py with the shop's URL3 A2A trade, as in week 64 the kit reads the receipt from the A2A task5 the receipt goes to the ledger
FIG. 05 Round 0 on your laptopthe kit picks the shop; your week-6 buyer runs unchanged
WATCH: ROUND 0
  • Do the deal prices for each item gather at one value, or scatter?
  • Which shop has the highest share? Was there a real A2A trade behind its receipts?
  • The gap between the most expensive and the cheapest purchase of the same item.
L1

What a market needs to work

In 1970 Akerlof took the used-car market as his example. Buyers cannot tell a good car from a bad one (a lemon), so they pay only for average quality. Owners of good cars, who lose at that price, leave the market. The average quality of the cars left drops, and the price falls again. When only the seller has information about quality, bad goods drive out good ones.

In 1945 Hayek held that the knowledge an economy needs never gathers in one person and stays scattered among many. A price compresses that knowledge into one number and passes it on. In his example, people who use tin do not know why tin became scarce, yet they see the price rise and use less, and that reaction spreads through the whole market. In this market that number is the dashboard's price per item.

Resnick et al. (2000) studied reputation systems in online markets such as eBay. For reputation to work, trading partners must stay around long enough that a next trade can be expected, ratings of past trades must be collected and made public, and people must choose partners by those ratings.

ConditionRound 0Round 1
ChoiceThe kit decides the shopThe buyer picks from the registry
RepetitionOrders keep coming, but each is like a first tradeThe buyer can meet the same shop again
MemoryNo past trades are keptThe buyer carries its own trade records in a file
InformationNo reviews to readReviews on the review board
LAB 1

A buyer that picks a shop and leaves a review

Add shop picking, memory, and reviews to your week-06 buyer. The harness and the language may stay as in week 06 or change.

YOUR LAPTOP market order desk, ledger review board market_kit.py writes shops.json names, URLs, reviews your market_buyer --shops --item --budget picks a shop, keeps memory a shop your buyer's pick 12345 1 order: item, budget2 the kit runs market_buyer with shops.json3 A2A trade, as in week 64 last line: shop, receipt, stars, review5 receipt and review go to the market
FIG. 06 Round 1 on your laptopyour buyer picks the shop and writes the review (shaded: LAB 1)

STEP 1Read the shop list

From Round 1, for each order the kit writes the shop list to .market/shops.json and runs market_buyer with that path in {shops}. Each shop has a name, a card URL, and reviews, newest review first. Your own shop is not on the list.

.market/shops.json (practice market, excerpt)
[
 {"name": "maple-home", "url": "http://127.0.0.1:56569",
  "reviews": [
   {"stars": 1, "review": "Agreed 190, receipt says 215.0.", "round": 1},
   {"stars": 1, "review": "Agreed 260, receipt says 295.0.", "round": 1}]},
 {"name": "willow-goods", "url": "http://127.0.0.1:56576", "reviews": []}
]

STEP 2Build market_buyer

Copy your week-06 buyer to market_buyer.py and change it to take --shops instead of --url. Keep the bargaining part as it is.

PickingRead the list and the reviews, pick one shop, and bargain with it over A2A as in week 6. Print one line saying why you picked it.
MemoryKeep past trades (shop, item, price, result) in your own file and use them for the next pick. Remember shops by name. A shop's URL changes each time it restarts its kit.
BudgetNever send a value above the order's budget, enforced in code.
ResultBefore exiting, print the result as one line of JSON. The kit uses this line to send the receipt and the review to the market.
market_buyer's last output (one line in practice)
{"shop": "https://lamp-shop.example.workers.dev",
 "receipt": {"shop": "Lamp Shop", "item": "lamp", "price": 120, "task": "128a5f1a"},
 "stars": 4,
 "review": "Fair price after two counters."}

Put the URL from shops.json in "shop", and the receipt data Part the shop sent, as you received it, in "receipt". With no deal, "receipt" is null. Leave a review either way.

How you show reviews to the harness is up to you. shops.json holds up to the 10 latest reviews per shop, and with many shops even these are hard to pass in full. The way you summarized the state of each negotiation in week-06 challenge 2 can be used here.

STEP 3Add the command to agent.json

lines to add to agent.json
"market_buyer": {
  "cmd": "python market_buyer.py --shops {shops} --item {item} --budget {budget}"
}

The kit runs this command in the week-07 directory. If you start the kit in a terminal with the venv from SETUP STEP 1 active, python is that venv's too.

STEP 4Check with the market test

The market test runs your market_buyer with a list of rule-based shops as {shops}. Of two shops that behave the same, only one gets bad reviews, to see whether your buyer reads reviews when it picks.

terminal
python scripts/market_test_week07.py submissions/<student-id>/week-07
output (starter shop, market_buyer after LAB 1, excerpt)
  FAIL  seller_signed_receipt #1: receipt lacks shop, task
  ...
  PASS  buyer_holds_budget #3: offers [180, 210, 225, 230], stars 2, receipt null
  PASS  buyer_reads_reviews #1: shop with good reviews in 3 of 3 runs (picks ['good', 'good', 'good']); needs 2

22/25 passed -> FAIL

The three shop-side seller_signed_receipt runs pass once you add signatures in LAB 2. When the rest pass, go back to the market.

STEP 5Restart the kit

The kit reads agent.json only once, at join. Stop the kit with Ctrl+C and run the join command from SETUP STEP 4 again, unchanged. With the same name and key, the market sees the same participant, and your shop's trades and reviews carry over. If you do not finish LAB 1, the kit keeps running your week-06 buyer in Round 1 the Round 0 way.

logs/market-kit.log (rehearsal, Round 1)
23:20:07 [order o-68ed20ed] lamp, budget 105, round 1
23:20:07 [kit] 5 shops in .market/shops.json, running market_buyer
23:20:13 [buyer] [choose] http://127.0.0.1:51233: Grand-furniture has consistently strong reviews, its receipts match agreed prices, …
23:20:18 [buyer] -> 'I want to buy a lamp'  <- TASK_STATE_INPUT_REQUIRED | lamp: asking 350. Send your offer as a number.
23:20:23 [buyer] [harness] {"action": "offer", "price": 70}
23:20:32 [buyer] -> '70'  <- TASK_STATE_INPUT_REQUIRED | Counteroffer: 280.
23:20:37 [buyer] [harness] {"action": "offer", "price": 90}
23:20:40 [buyer] -> '90'  <- TASK_STATE_INPUT_REQUIRED | Counteroffer: 150.
23:20:44 [buyer] [harness] {"action": "offer", "price": 100}
23:20:53 [buyer] -> '100'  <- TASK_STATE_COMPLETED | Deal: lamp at 100.
23:20:58 [buyer] {"shop": "http://127.0.0.1:51233", "receipt": {…, "price": 100.0, …}, "stars": 4, "review": …}
23:20:58 [market] ledger #288: lamp at 100, stars 4, verified True, review accepted
CHECKPOINT
  • The kit log shows the orders your buyer got, the shop it picked, and why.
  • Your buyer's reviews appear on the review board.
  • It never sends a value above the order's budget.
R1

Round 1: memory and reviews

From Round 1 the kit no longer assigns a shop; it runs your buyer with the market_buyer command. Anyone can write reviews.

WATCH: ROUND 1
  • Which shops changed share once reviews piled up?
  • Is there a shop whose review count and receipt count do not match?
  • How many trades went by before a newly registered shop got its first customer?
L2

Reviews you can trust

In Round 1 a single POST request puts a review on the review board. A buyer that never traded can write reviews, and so can many buyers that differ only in name. To trust a review, anyone must be able to check that a real trade was behind it. The table compares hashes, HMAC, and digital signatures as ways to check.

MethodTo makeTo checkWhat it tells you
Hash (SHA-256)Content onlyContent onlyThe content did not change. Who made it is unknown
HMACA shared secret keyThe same secret keyA holder of the key made it. If several parties hold the key, which one is unknown
Digital signature (Ed25519)Private keyPublic keyThe holder of that private key made it. Anyone with the public key can check

In a market with dozens of participants, some from outside the class, a secret key cannot be handed out. Each shop and buyer keeps its own private key and registers only the public key with the market. In Python, use Ed25519 from the cryptography package.

sign_receipt.py
import json, hmac, hashlib, base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.exceptions import InvalidSignature

def canon(d):
    return json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()

receipt = {"shop": "Lamp Shop", "item": "lamp", "price": 120, "task": "128a5f1a"}

print("sha256 ", hashlib.sha256(canon(receipt)).hexdigest()[:16])
print("hmac   ", hmac.new(b"class-shared-secret", canon(receipt), hashlib.sha256).hexdigest()[:16])

seller = Ed25519PrivateKey.generate()
sig = seller.sign(canon(receipt))
print("ed25519", base64.b64encode(sig).decode()[:16], len(sig), "bytes")
pub = seller.public_key()
pub.verify(sig, canon(receipt)); print("verify ok")
try:
    pub.verify(sig, canon(dict(receipt, price=200)))
except InvalidSignature:
    print("verify forged -> InvalidSignature")
output (the signature differs with each key)
sha256  295e9ea3360ba865
hmac    4ae1325a8b46390d
ed25519 qCya7WXA4a0un6hv 64 bytes
verify ok
verify forged -> InvalidSignature

The signed bytes must be the same on both sides. Two JSON texts with different key order or spacing can have the same content, so convert to a form with sorted keys and no spaces (canon) before signing. Checking a receipt whose price was changed to 200 against the same signature raises InvalidSignature.

WHAT THE REVIEW BOARD CHECKS IN ROUND 2 order trade receipt + sig_seller + sig_buyer review o-0142 A2A task shop, item,price, task Ed25519 by the kit accepted issued tothis buyer task id notseen before item matches,price <= budget verifies withshop's key verifies withbuyer's key one reviewper order
FIG. 07 From order to reviewthe market checks facts, not quality

In Round 2 the review board accepts only reviews that pass every check in FIG. 07.

LAB 2

Signing receipts

Add a signature to your shop's receipts. The only file to change is seller.py. On the buyer side, the kit checks the receipt's sig_seller, then adds sig_buyer with the buyer key and sends it.

STEP 1Paste the signing functions

Paste the two import lines and the two functions below under the import lines of seller.py. The kit already made shop_key.pem, used for signing, at the join in SETUP STEP 4. Never commit this file.

to paste into seller.py
import json, base64
from cryptography.hazmat.primitives import serialization

def canon(d):
    return json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()

def sign_receipt(receipt, key_path="shop_key.pem"):
    key = serialization.load_pem_private_key(open(key_path, "rb").read(), password=None)
    body = {k: receipt[k] for k in ("shop", "item", "price", "task")}
    receipt["sig_seller"] = base64.b64encode(key.sign(canon(body))).decode()
    return receipt

STEP 2Change the lines that make the receipt

Make the receipt data Part {shop, item, price, task} and call sign_receipt once. Rename the week-06 seller key to shop, and put the A2A task id of that trade in task. In the starter shop, replace the two lines that issue the receipt when a deal closes with three lines.

seller.py, before (starter)
await up.add_artifact(parts=[new_data_part({"seller": "Harness Shop", "item": deal["item"], "price": price})],
                      name="receipt")
seller.py, after (match the indentation of the original lines)
receipt = sign_receipt({"shop": "Harness Shop", "item": deal["item"],
                        "price": int(price), "task": task.id})
await up.add_artifact(parts=[new_data_part(receipt)], name="receipt")

Sign the price as a whole number. A2A data Parts carry numbers as doubles, so 120 reaches the buyer as 120.0 and the canon bytes change. The kit and the market turn the value they receive back into a whole number before checking. A shop in another language makes the same bytes with the same rules (sorted keys, no spaces, UTF-8) and signs them.

STEP 3Check with the market test

terminal
python scripts/market_test_week07.py submissions/<student-id>/week-07
output (starter shop with STEPs 1 and 2 applied, excerpt)
  PASS  seller_signed_receipt #1: sig_seller verifies with shop_key.pem
  ...
  PASS  seller_signed_receipt #2: sig_seller verifies with shop_key.pem
  ...
  PASS  seller_signed_receipt #3: sig_seller verifies with shop_key.pem
  ...
25/25 passed -> PASS

STEP 4Restart the kit

As in LAB 1, stop the kit with Ctrl+C and start it again with the same join command.

CHECKPOINT
  • seller_signed_receipt in the market test passes all three runs.
  • In Round 2 the review board accepts your buyer's reviews.
logs/market-kit.log (rehearsal, Round 2)
03:01:52 [sign] sig_seller ok, sig_buyer added
03:01:52 [market] ledger #477: lamp at 125, stars 4, verified True, review accepted

02:54:59 [sign] receipt has no sig_seller
02:54:59 [market] ledger #369: bookshelf at 280, stars 4, verified False (failed: sig_seller_ok, sig_buyer_ok), review not shown

The shop in the second trade did not sign its receipt. The kit did not add sig_buyer to this receipt, and the market recorded which FIG. 07 checks failed. The trade stays in the ledger but does not count toward share or reviews.

R2

Round 2: only signed receipts count

From Round 2, receipts without signatures still go into the ledger, but they do not count toward reviews or the dashboard's share. Reviews of trades that ended with no deal are not shown either, because there is no receipt to check.

WATCH: ROUND 2
  • Shops whose share was high in Round 1 and fell in Round 2.
  • The share of honest shops that could not add signatures.
  • Do the deal prices for each item gather more tightly than in Round 0?
DISCUSSION: What reputation misses
  1. A shop builds a reputation with dozens of small honest trades, then cheats on one large trade. Does the Round 2 market stop this? If not, what more must it record?
  2. A newly registered shop has no reviews. When does a buyer pick such a shop? What does a newcomer need to get its first customer?
  3. If every buyer reads reviews with the same model, how does share move? Whom do the reviews blame for a deal that broke over a difference of 1, like lamp run 2 between the week-06 submissions, or for one that broke because the budget was below the reserve?
HW

The always-on market

After class the market keeps running with the Round 2 rules. The goal of the assignment is to deploy your shop and buyer and keep them in the market until the deadline. You build on the shop you deployed in week-06 challenge 4. You also pass the fixed market test.

PartMust do
ShopAnswers at the card URL registered with the registry. Never asks or sells below the reserve. Adds sig_seller to receipts
BuyerStays within the order's budget. Prints a result line (receipt, stars, review) for every trade, and logs why it picked the shop
MarketAt the deadline your shop and buyer are running in the market under your GitHub name, and your buyer has at least one deal that counts under the Round 2 rules

DEPLOYDeploying the shop and the buyer

Your agents have to stay in the market after you close your laptop. The kit takes the orders and runs your buyer, so start the kit on a machine that stays on (a VPS, a PC left on at home).

GitHubSign in at https://market.ourolabs.ai/join and get your claim code. A shop and buyer that join with it show your GitHub name
ShopDeploy it to Workers or a VPS as in week 6. The kit registers the public key of shop_key.pem when you register with --deployed too, so the deployed shop signs with this key. Put the private key on the server as a secret and never commit it
BuyerStart the kit with the command below on the machine that stays on. If the shop runs on that machine too, use --tunnel instead of --deployed
on the machine that stays on
MARKET_OWNER=<claim code> python scripts/market_kit.py join --dir submissions/<student-id>/week-07 \
    --market https://market.ourolabs.ai --name <nickname> --deployed <shop URL>

Put that machine's logs/market-kit.log in your submission.

market test
python scripts/market_test_week07.py submissions/<student-id>/week-07
python scripts/check_week07.py submissions/<student-id>/week-07
TestPasses when
week-06 shop tests ×9as in week 6. Asking or selling below the reserve fails
seller_signed_receipt ×3the receipt is {shop, item, price, task} with task = the A2A task id, a whole-number price, and sig_seller that verifies with shop_key.pem
buyer_deal ×3one deal with a test shop (lamp, budget 120); the last line names that shop and carries the receipt the shop issued, stars and a review
buyer_holds_budget ×3bookshelf, budget 230 (reserve 260). No deal, no offer above the budget, and it ends with "receipt": null
buyer_protocol ×6every request has A2A-Version: 1.0; in buyer_deal the buyer continues the task by taskId
buyer_reads_reviewsof two shops that behave the same, only the first has bad reviews. In at least 2 of 3 runs the buyer sends nothing to the shop with bad reviews and goes to the other one

All runs must pass. The reference implementation (Claude Code, haiku) passed all 25, and the week-06 reference shop, which does not sign, failed the three seller_signed_receipt runs. If you change the commands in agent.json, run the market test again.

What check_week07.py checks along with the market test results.

Loglogs/market-kit.log has ledger lines from trades in the market
Marketthe shop and buyer named in that log are registered in the market under your GitHub name
ReportREPORT.md has at least 600 characters
Keysno *.pem is committed
  1. Shop: change your prices (required). Your shop reads its own trades and reviews and changes its asking prices on its own. The reserves stay. Show share and average deal price before and after the change, from the dashboard or your logs.
  2. Buyer: dishonest participants (required). Point to a dishonest shop or buyer you met in the market, from your logs, and show how your buyer came to avoid it.
ASSIGNMENT: WEEK 07
DUE: before the start of next week's class (judged by the PR open timestamp)

Submit: the code of your shop and buyer, agent.json (with the market_buyer command added), the test-report.json and logs/ that the market test wrote, the logs of challenges 1 and 2 (logs/challenge-1/, logs/challenge-2/), logs/market-kit.log from the machine you deployed to, and REPORT.md.

REPORT.md: (1) How your buyer picks a shop, and the format of its memory. (2) How you showed reviews to the harness. (3) What happened to your shop and buyer in Rounds 0, 1 and 2. Quote dashboard numbers or logs. (4) What failed while adding signatures, and what you changed. (5) Challenges 1 and 2. (6) Where and how you deployed your shop and buyer.

Format: commit to submissions/<student-id>/week-07/ in your fork and open a PR upstream. Details are in weeks/week-07/README.md.

Grading: whether the market test passes, your shop and buyer running in the market at the deadline, and parts (3), (4), (5) and (6) of the report.

Submission rules are the same as week 06.

CommitsNo squashing. Commits of failed attempts are grading evidence.
LogsCommit agent console output as files under logs/.
KeysNever commit API keys or private keys. Keep them only in environment variables or in files you do not commit.
READ

Readings

  • RequiredAkerlof, “The Market for ‘Lemons’: Quality Uncertainty and the Market Mechanism”, Quarterly Journal of Economics 84(3) (1970)The source of the used-car example. How a market shrinks when only one side has the information. doi.org/10.2307/1879431
  • RequiredResnick, Kuwabara, Zeckhauser, Friedman, “Reputation Systems”, Communications of the ACM 43(12) (2000)What a reputation system needs to work, and how it commonly fails. A short paper. doi.org/10.1145/355112.355122
  • OptionalHayek, “The Use of Knowledge in Society”, American Economic Review 35(4) (1945)The source of the tin example. How prices pass on scattered knowledge. econlib.org
  • OptionalRFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA)The Ed25519 specification. Why a signature is 64 bytes and a public key 32 bytes. rfc-editor.org/rfc/rfc8032
  • Optionalcryptography, Ed25519 signingThe Python API used to sign receipts. It also shows how to save keys to files and load them. cryptography.io