Skip to content

Commit a1701b9

Browse files
committed
feat: make txodds live agents explicit
1 parent 994b5fc commit a1701b9

14 files changed

Lines changed: 1563 additions & 115 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ bun.lockb
3737
coverage*.json
3838
.vercel
3939

40+
# Local TxODDS run ledgers
41+
examples/txodds/data/
42+
4043
# Reference repos cloned for analysis (not part of project)
4144
ref/
4245

coral-agents/buyer-agent/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const ARGS = (process.env.BUYER_ARGS || process.env.BUYER_ARG || 'SOL-USDC').spl
4040
const ARG = ARGS[0]
4141
const BID_WINDOW_MS = Number(process.env.BID_WINDOW_MS ?? '5000')
4242
const CYCLE_MS = Number(process.env.CYCLE_INTERVAL_MS ?? '30000')
43-
const SELLERS = (process.env.MARKET_SELLERS ?? 'seller-worldcup,seller-fast,seller-premium')
43+
const SELLERS = (process.env.MARKET_SELLERS ?? 'seller-worldcup,seller-fast,seller-premium,seller-risk-policy,seller-fan-card')
4444
.split(',').map((s) => s.trim()).filter(Boolean)
4545
// F3: the payout wallet the buyer expects (personas share one in the demo). If set, the buyer refuses
4646
// to deposit to an ESCROW_REQUIRED whose seller= pubkey differs - binding the award to the payout.

coral-agents/seller-agent/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ In arbiter mode, the seller verifies the escrow buyer as the vault PDA from `DEP
2222
|---|---|
2323
| `src/index.ts` | Coral market loop and funding verification. |
2424
| `src/escrow.ts` | Read-only escrow funding check. |
25-
| `src/service.ts` | TxODDS service delivery. |
25+
| `src/service.ts` | Service delivery for `txline`, `risk-policy`, `fan-card`, and `freelance`. |
2626
| `src/payment.ts` | Older direct-payment helper/tests. |
2727
| `src/replay.ts` | Replay helpers/tests. |
2828

@@ -65,7 +65,7 @@ The Pay.sh rail is a simulated proof-adapter rail until a live provider API is i
6565
|---|---|
6666
| `SELLER_WALLET` | Payout address. |
6767
| `AGENT_NAME` | Agent/persona name. |
68-
| `SERVICES` | Supported services, usually `txline`. |
68+
| `SERVICES` | Comma-separated supported services. Current coded services are `txline`, `risk-policy`, `fan-card`, and `freelance`. |
6969
| `FLOOR_SOL` | Minimum bid. |
7070
| `PERSONA` | Persona label/config. |
7171
| `SETTLEMENT_MODE` | `arbiter` or `direct`. |

coral-agents/seller-agent/src/service.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
22
import { deliverService } from './service.js'
33

4-
describe('deliverService txline-only routing', () => {
4+
describe('deliverService routing', () => {
55
const realFetch = global.fetch
66

77
beforeEach(() => {
@@ -19,7 +19,38 @@ describe('deliverService txline-only routing', () => {
1919

2020
it('rejects legacy generic services', async () => {
2121
const out = JSON.parse(await deliverService('coingecko eth'))
22-
expect(out).toEqual({ error: 'unsupported service', service: 'coingecko', supported: ['txline', 'freelance'] })
22+
expect(out).toEqual({
23+
error: 'unsupported service',
24+
service: 'coingecko',
25+
supported: ['txline', 'freelance', 'risk-policy', 'fan-card'],
26+
})
27+
})
28+
29+
it('delivers a deterministic fixture risk policy', async () => {
30+
const out = JSON.parse(await deliverService('risk-policy edge 18175397'))
31+
expect(out).toMatchObject({
32+
service: 'risk-policy',
33+
fixtureId: '18175397',
34+
policy: {
35+
action: 'observe',
36+
maxExposureSol: 0,
37+
},
38+
})
39+
expect(out.policy.requires).toContain('verifier pass before escrow release')
40+
expect(out.policy.guardrails).toContain('no real-money wagering')
41+
})
42+
43+
it('delivers a deterministic fan explainer card', async () => {
44+
const out = JSON.parse(await deliverService('fan-card edge 18175397'))
45+
expect(out).toMatchObject({
46+
service: 'fan-card',
47+
fixtureId: '18175397',
48+
card: {
49+
audience: 'fan',
50+
},
51+
limits: ['educational summary', 'not betting advice'],
52+
})
53+
expect(out.card.explainer).toContain('break-even fair line')
2354
})
2455

2556
it('freelance without an LLM key returns an honest error payload (verifier fails it, no release)', async () => {

coral-agents/seller-agent/src/service.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,82 @@
11
/**
22
* Seller services.
33
*
4-
* `txline` — the headline product: a verified TxLINE fair-line read for a fixture.
5-
* `freelance` — the generic LLM worker (the freelancer market's baseline seller): the brief goes to
4+
* `txline` - verified TxLINE fair-line reads for a fixture.
5+
* `risk-policy` - deterministic policy guardrails for a fixture/order.
6+
* `fan-card` - deterministic fan-facing explanation card for a fixture/order.
7+
* `freelance` - the generic LLM worker (the freelancer market's baseline seller): the brief goes to
68
* the LLM, the deliverable comes back as JSON. Without an LLM key it returns an error payload, which
7-
* the verifier fails and the buyer refuses to pay for no-capability sellers don't get released.
9+
* the verifier fails and the buyer refuses to pay for - no-capability sellers don't get released.
810
*/
911
import { complete, parseJsonReply } from '@pay/agent-runtime'
1012

1113
const TXLINE_BASE = process.env.TXLINE_BASE_URL || 'https://txline-dev.txodds.com'
14+
const SUPPORTED_SERVICES = ['txline', 'freelance', 'risk-policy', 'fan-card']
1215

1316
export async function deliverService(request: string): Promise<string> {
1417
const [first, ...rest] = request.trim().split(/\s+/).filter(Boolean)
1518
const service = (first ?? 'txline').toLowerCase()
1619
if (service === 'freelance') return freelanceService(rest.join(' '))
20+
if (service === 'risk-policy') return riskPolicyService(rest.join(' '))
21+
if (service === 'fan-card') return fanCardService(rest.join(' '))
1722
if (service !== 'txline') {
18-
return JSON.stringify({ error: 'unsupported service', service, supported: ['txline', 'freelance'] })
23+
return JSON.stringify({ error: 'unsupported service', service, supported: SUPPORTED_SERVICES })
1924
}
2025
return txlineService(rest.join(' '))
2126
}
2227

28+
function fixtureIdFrom(request: string): string | undefined {
29+
return request.trim().split(/\s+/).find((token) => /^\d+$/.test(token))
30+
}
31+
32+
async function riskPolicyService(request: string): Promise<string> {
33+
const fixtureId = fixtureIdFrom(request)
34+
return JSON.stringify({
35+
service: 'risk-policy',
36+
...(fixtureId ? { fixtureId } : {}),
37+
request: request || 'unspecified',
38+
policy: {
39+
action: fixtureId ? 'observe' : 'no-action',
40+
maxExposureSol: 0,
41+
requires: [
42+
'verified TxLINE fair-line payload',
43+
'buyer budget policy pass',
44+
'verifier pass before escrow release',
45+
],
46+
guardrails: [
47+
'devnet only',
48+
'no real-money wagering',
49+
'no automated sportsbook execution',
50+
],
51+
},
52+
rationale: fixtureId
53+
? `Fixture ${fixtureId} can be analyzed after verified fair-line delivery.`
54+
: 'No fixture id supplied.',
55+
})
56+
}
57+
58+
async function fanCardService(request: string): Promise<string> {
59+
const fixtureId = fixtureIdFrom(request)
60+
const target = fixtureId ? `fixture ${fixtureId}` : 'the selected fixture'
61+
return JSON.stringify({
62+
service: 'fan-card',
63+
...(fixtureId ? { fixtureId } : {}),
64+
request: request || 'unspecified',
65+
card: {
66+
title: `Fair-line explainer for ${target}`,
67+
audience: 'fan',
68+
explainer: 'TxODDS provides a break-even fair line. A value claim needs an outside book price above that fair price.',
69+
sections: [
70+
{ label: 'What was bought', value: 'verified fair-line context' },
71+
{ label: 'What it is not', value: 'not a sportsbook recommendation' },
72+
{ label: 'Proof path', value: 'hash-bound delivery, verifier verdict, devnet escrow release' },
73+
],
74+
shareCopy: `Agent-delivered fair-line context for ${target}; verification and settlement are recorded in the run ledger.`,
75+
},
76+
limits: ['educational summary', 'not betting advice'],
77+
})
78+
}
79+
2380
async function freelanceService(brief: string): Promise<string> {
2481
try {
2582
const text = await complete({

coral-agents/verifier-agent/src/verify.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ describe('checkDelivery - deterministic checks decide first', () => {
3434
expect(v.sha).toBe(sha256Hex(payload))
3535
})
3636

37+
it('passes txline edge payloads for the requested fixture before consulting the LLM', async () => {
38+
const txline = '{"service":"txline-edge","fixtureId":"12345","analysis":{"call":"Home value","confidence":0.7}}'
39+
const v = await checkDelivery(req({ payload: txline, sha: sha256Hex(txline) }), 'v', llmSays('{"pass":false,"reason":"too literal"}'))
40+
expect(v).toMatchObject({ verdict: 'pass', reason: 'hash + txline fixture verified', by: 'v' })
41+
})
42+
3743
it('honours an LLM fail verdict on structurally valid payloads', async () => {
3844
const v = await checkDelivery(req(), 'v', llmSays('{"pass":false,"reason":"does not answer the arg"}'))
3945
expect(v).toMatchObject({ verdict: 'fail', reason: 'does not answer the arg' })

coral-agents/verifier-agent/src/verify.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ export async function checkDelivery(req: VerifyRequest, name: string, llm: Llm =
2828
const err = String((data as Record<string, unknown>).error).slice(0, 40)
2929
return { ...base, verdict: 'fail', reason: `payload reports error: ${err}` }
3030
}
31+
const structured = data && typeof data === 'object' ? data as Record<string, unknown> : undefined
32+
if (
33+
req.service === 'txline' &&
34+
structured?.service === 'txline-edge' &&
35+
String(structured.fixtureId ?? '') === req.arg
36+
) {
37+
return { ...base, verdict: 'pass', reason: 'hash + txline fixture verified' }
38+
}
3139

3240
try {
3341
const parsed = parseJsonReply<{ pass?: boolean; reason?: string }>(await llm({

examples/txodds/coral/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ This folder launches the TxODDS service as a CoralOS multi-agent session. A buye
66

77
```text
88
buyer-agent
9-
-> WANT service=txline arg="edge <fixtureId>"
9+
-> WANT service=txline arg=<fixtureId>
1010
1111
seller-worldcup
1212
seller-fast
1313
seller-premium
14+
seller-risk-policy
15+
seller-fan-card
1416
-> BID price=<sol> by=<agent>
1517
1618
buyer-agent
@@ -21,6 +23,10 @@ buyer-agent
2123
-> DEPOSITED settlement=arbiter vault=<vault PDA>
2224
seller
2325
-> DELIVERED payload=<json>
26+
buyer-agent
27+
-> VERIFY sha=<delivery hash>
28+
verifier-agent
29+
-> VERIFIED verdict=pass
2430
buyer-agent
2531
-> ARBITER_RELEASED sig=<devnet tx>
2632
```
@@ -55,6 +61,16 @@ npm run coral
5561

5662
`round.ts` reads a live fixture id from the proxy's `/api/board` when available, starts the buyer and seller personas, and injects `SETTLEMENT_MODE=arbiter`.
5763

64+
Default sellers:
65+
66+
| Agent | Services |
67+
|---|---|
68+
| `seller-worldcup` | `txline` |
69+
| `seller-fast` | `txline` |
70+
| `seller-premium` | `txline` |
71+
| `seller-risk-policy` | `txline`, `risk-policy` |
72+
| `seller-fan-card` | `txline`, `fan-card` |
73+
5874
## Logs
5975

6076
CoralOS names containers by generated ids. Find and tail by image:

0 commit comments

Comments
 (0)