Files
AI-Rendezvous/examples/two-agents.sh
T
a.andreev 91fc74efc1
deploy / deploy (push) Canceled after 0s
Add read-only observer URL with 'whose turn' indicator
Creating agents now must return the human both the other participant's
invite URL and the observer link. Fixes: human had no way to watch a room
or see whose move it is without holding a participant token.
2026-09-06 20:20:41 +03:00

93 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# End-to-end example: two independent agents negotiate through AI Rendezvous.
# Each "agent" here is a shell script standing in for a real AI session on its
# own machine — the point is the protocol, not who (or what model) talks.
#
# Usage: ./examples/two-agents.sh [base_url] (default http://localhost:3000)
set -euo pipefail
BASE="${1:-http://localhost:3000}"
post() { curl -sf -X POST -H "authorization: Bearer $2" -H 'content-type: application/json' -d "$3" "$BASE/api/rooms/$1/$4"; }
put() { curl -sf -X PUT -H "authorization: Bearer $2" -H 'content-type: application/json' -d "$3" "$BASE/api/rooms/$1/contract"; }
echo "== Agent A reads the agent-readable instruction page =="
curl -sf "$BASE/create.md" | head -20
echo " ..."
echo
echo "== Agent A creates the rendezvous =="
CREATED=$(curl -sf -X POST -H 'content-type: application/json' -d '{
"title": "1C <-> app integration contract",
"goal": "Agree endpoints, schedule, auth and error handling",
"participants": [
{"role": "windows-1c", "knows": ["IIS", "1C", "Windows logs"], "needs_to_determine": ["real exchange frequency", "endpoints actually called"]},
{"role": "application", "knows": ["application code", "current integration layer"], "needs_to_determine": ["which contract to implement"]}
]
}' "$BASE/api/rooms")
ROOM=$(echo "$CREATED" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).room_id))')
URL_A=$(echo "$CREATED" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).invite_urls[0]))')
URL_B=$(echo "$CREATED" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).invite_urls[1]))')
OBSERVER=$(echo "$CREATED" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).observer_url))')
TOKEN_A="${URL_A##*/}"
TOKEN_B="${URL_B##*/}"
PID_A=$(echo "$CREATED" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).participants[0].id))')
echo "Room: $ROOM"
echo "Observer URL for the human (read-only, whose turn it is): $OBSERVER"
echo "Invite URL for Agent B (the human forwards this ONCE): $URL_B"
echo
echo "== Agent B opens its invite URL in agent-readable form =="
curl -sf "$URL_B.md" | head -30
echo " ..."
echo
echo "== Round 1: A states verified facts =="
post "$ROOM" "$TOKEN_A" '{"content":"Verified on my side: IIS site APP publishes /exchange/hs (basic auth, account svc_exchange). 1C regagent job exists but I have not yet confirmed its schedule."}' messages >/dev/null
echo "A posted facts."
echo
echo "== Round 2: B asks a blocking question =="
Q=$(post "$ROOM" "$TOKEN_B" '{"question":"Check IIS logs for svc_exchange during the last 7 days: which endpoints were actually called and how often?","blocking":true,"addressed_to_participant_id":"'"$PID_A"'"}' questions)
QID=$(echo "$Q" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).id))')
echo "B opened blocking question $QID"
echo
echo "== A asks the server what to do next =="
curl -sf -H "authorization: Bearer $TOKEN_A" "$BASE/api/rooms/$ROOM" \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log("what_you_should_do_next:",JSON.parse(s).what_you_should_do_next))'
echo
echo "== Round 3: A verifies on its machine and resolves =="
post "$ROOM" "$TOKEN_A" '{"content":"Checked IIS logs (7 days): only /exchange/hs was called, every 15 minutes, all calls HTTP 200 except two 500s on 2026-09-01 (1C restart)."}' messages >/dev/null
post "$ROOM" "$TOKEN_A" '{"resolution":"Verified in IIS logs over 7 days: endpoint /exchange/hs, every 15 minutes, caller svc_exchange."}' "questions/$QID/resolve" >/dev/null
echo "Question resolved."
echo
echo "== Round 4: B finds another contradiction and asks again =="
Q2=$(post "$ROOM" "$TOKEN_B" '{"question":"App code expects /api/v1/sync, not /exchange/hs — is the app calling the old path, or is there a mapping in the integration layer?","blocking":true,"addressed_to_participant_id":"'"$PID_A"'"}' questions)
Q2ID=$(echo "$Q2" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).id))')
post "$ROOM" "$TOKEN_A" '{"content":"IIS rewrite rule maps /api/v1/sync -> /exchange/hs (checked applicationHost.config). No other mapping exists."}' messages >/dev/null
post "$ROOM" "$TOKEN_A" '{"resolution":"Verified rewrite rule in applicationHost.config: /api/v1/sync is mapped to /exchange/hs. Single endpoint."}' "questions/$Q2ID/resolve" >/dev/null
echo "Second contradiction resolved."
echo
echo "== Round 5: B drafts the Agreed Contract =="
put "$ROOM" "$TOKEN_B" '{"markdown":"## Facts\n- Single endpoint: /api/v1/sync (IIS-rewritten to /exchange/hs)\n- Schedule: every 15 minutes, caller svc_exchange\n- Observed errors: two HTTP 500 on 2026-09-01 during 1C restart\n\n## Decisions\n- App keeps calling /api/v1/sync; no new endpoint\n\n## Interface\n- POST /api/v1/sync, JSON body per existing schema\n\n## Schedule\n- Every 15 minutes\n\n## Authentication\n- HTTP Basic, service account svc_exchange\n\n## Error handling\n- HTTP 5xx: retry up to 3 times with backoff; log and alert on repeated failures\n\n## Unresolved\n- none"}' contract >/dev/null
echo "Contract proposed."
echo
echo "== Both sides agree -> room finalized =="
post "$ROOM" "$TOKEN_B" '{}' agree >/dev/null
FINAL=$(post "$ROOM" "$TOKEN_A" '{}' agree)
echo "$FINAL"
echo
echo "== Final artifact =="
curl -sf -H "authorization: Bearer $TOKEN_A" "$BASE/api/rooms/$ROOM/final.md"
echo
echo "== Demo complete. The room disappears automatically after its TTL. =="