Pool(): your marketplace

The running jobjob_7Hq2 · waiting · on approval · $49.00 held

One kind of people, verified once, offered only work they can do, paid for every act and never charged to work. A role names a Pool, and the Pool fills it.

A Pool answers one question: who may take a role's work, and how does it reach them?

AI does every step it can, and a Pool member is the rung above your staff in who does the step. Where you want a person, or the work needs a credentialed one, a role names a Pool. Each step that role does posts a Task, and the Pool's members claim it. A Pool is your marketplace for one kind of people: verified once, offered only work they can do, and paid for every act.

Four keys: its name, the roles it fills, the application and the verifier. A role on Translation names it, and a rules row reserves the signature to the credential:

Run
cat pools/translators.ts
magic values: none
import { Pool } from 'services.dev'

export const translators = Pool({
  name: 'translators',
  roles: { translator: { credential: 'sworn translator' } },
  join: { name: 'text', languages: 'language[]' },
  verify: 'https://verify.example.test/translators',
})
Run
cat services/translation.ts
magic values: none
import { translators } from '../pools/translators.ts'
  roles: { translator: { pool: translators } },
  deliver: {
    translate: 'ai',
    sign: { by: 'translator', stamp: true },

check reads the Pool back, and dev serves its portal, its /join and its verifier's write path:

Run
npx services.dev check
magic values: none
pools/translators.ts · 4 lines · translators · 1 role: translator · joins in 1 step · feed · hold 24h
  roles   translator · sworn translator · from translators
  bounds  reserved sign to sworn translator
  apps    customer portal · delivery portal · owner console
✓ ready to sell · 1 Service · it keeps at least 80% of every job
Run
npx services.dev dev --no-try
magic values: none
  translators  http://localhost:8787/translators · join at /translators/join, or npx services.dev members add translators <email>
  verifier     vt_test_7Hq2 · the only writer of verified facts, at POST /<pool>/members/:id/verify
  • Work they can do. Eligibility is derived from the role and the rules table, never declared. The first predicate a member fails comes back with its cure.
  • An offer, never an assignment. A Task reaches members by its dispatch, at a fee fixed when it posts. Only the fill ladder moves it, and only up.
  • Their floor, their hours. Their minimum fee, areas, hours and the brands they work for filter what they are offered, in one feed order for every brand.
  • Only what they need, when they need it. A teaser, then a sheet, then the packet, as What a member sees shows.
  • Their conflicts kept theirs. A hashed ledger no one else reads.
  • Paid for every act, on a clock they can read. A send-back or a decline is paid like an attestation, and Pay says when each dollar is theirs.
  • Their record, their own. A track record signed by the door, which they carry and share with another door's Pool when they choose.

No line charges a member per Task, per lead or by a percentage, and check fails member_charged on any line a member would pay. The one member charge a Pool can carry is a flat software fee, declared in the Pool as fee and shown on the door before anyone joins. It is the first line of the member charter, which no Pool, importer or door can weaken.

Credentialed steps are how a Service meets licensing by design. The rules table reserves an act to a credential, and only a member who holds it, verified and where the act needs it, can claim the Task.

The buyer pays the job's price. The member's fee is one line of it, paid for the act, and the brand keeps what's left after AI. A raise the ladder makes comes out of the brand's share, and the cap keeps a brand-funded raise from losing money. Here the ladder lifted a $50 fee to $55, and the buyer still paid the $99 they were quoted:

captured/legal-fill.txt · magic values: client.email = signs@example.test
→ POST /matters/job_7Hq2/accept
← job_7Hq2 · accepted · flags.pdf · memo.pdf · $99.00 final
  ledger  +$99.00 · attorney's fee $55.00, sold by the attorney (rail member) · brand $44.00 · AI $1.26 · you $42.74

Two Services, or two brands, that name one Pool share its members and one queue. The legal example's five Services all name its one Pool, each at its own fee:

captured/legal-check.txt · magic values: none
$ npx services.dev check
services/contract-review.ts · 16 lines · $99 per job, within 48 hours, charged on acceptance.
  roles   attorney · bar admission · from attorneys · $50 an act
services/demand-letter.ts · 26 lines · $149 per job, within 48 hours, charged on send's receipt.
  roles   attorney · bar admission · from attorneys · $75 an act
services/docket-watch.ts · 10 lines · $12 per month, charged at the start of each month.
  roles   watcher · patent attorney or agent · * · from attorneys · $4 an act
services/formation.ts · 30 lines · $299 per job, within 48 hours, charged on form_entity's receipt.
  roles   attorney · bar admission · from attorneys · $60 an act

Its captures speak the brand's own words, as your brand's words shows:

legal's wordsmatter is a jobasset is a subjectreview is a tasksigner is a membernetwork is a pooloutcome is a end

Each key links to the section that explains it.

KeyWrittenLeft outDoes
namestringrequiredThe portal's name: its path, /<name>, and its people.
doorstring/<name> on the Service's brandWhere members join and work: a domain your account operates, or an existing door, pinned at its terms' version.
register'expert' | 'work''expert'What members deliver: an approval on a packet, or a completed Task at a site, in a window.
roles{ [K in R]: PoolRole }requiredThe roles this Pool fills, each with its credential expression and defaults. A Service names one by its key.
scopes{ [K in C]?: '*' | 'region' }'region'Each credential's scope: '*' (one admission counts everywhere) or 'region'. A credential the roles name and this leaves out fails credential_unscoped.
joinShaperequiredThe application, field by field, in the type words. The feed reads areas, min_fee (dollars), hours and turnaround when it asks them.
verifyUrl | FnrequiredThe verifier: the only writer of verified facts, on its write path with its vt_ token.
freshWithin'30d'Re-verify every: freshness is read at use, so time alone makes a fact stale.
sees{ teaser: string[]; sheet?: string[] }{ teaser: ['act', 'region', 'fee_usd'] }What a member sees: the teaser before claiming (always with est_minutes, rate and due, never a party name), and the sheet.
clearstring[] | { attest: string[]; check?: string[] }noneWhat a member attests before the packet opens, and the party inputs checked, hashed, against their ledger.
holdWithin | { claim; then: 'touch' }max(24h, 3 × est)How long a claim holds before it reopens, never past its due: a span, or renewed by each touch. A member may extend it once.
windowShortSpan'1h'How long one member has a cascade's offer, or a first refusal, to themselves.
sheetShortSpan'10min'How long one member has a sheet to themselves.
jump{ min?: number }{ min: 3 }When passes move the ladder: at least min distinct sheet viewers since the last rung, more than half passing for one reason.
killPct'50%'The share of its fee a member keeps when a Task they cleared is withdrawn.
affinityWithinnoneFirst refusal on follow-on work for the same subject.
rank('fit' | 'track' | 'speed' | 'price')[]['fit', 'track', 'speed']How cascade ranks members. A member on probation ranks by fit and speed alone.
dispatchDispatch'feed', or 'cascade' when clear.check names partiesHow this Pool's Tasks reach members.
fillFillRung[] | falseraise 10% at 2h, widen at 4h, firms at 8h, staff at 12hThe fill ladder, for a Task no one claims. A raise is of the posted fee, never lowered; a rung never widens past eligibility. false waits, with an alarm.
fill_capPctthe lower of 50% and what the job's margin leavesRaises stop here, over the posted fee, so a brand-funded raise never loses money.
tiersTier[]noneTiers above eligibility, low to high, computed from the track record: a Task offers to the top eligible tier first.
probation{ tasks: number; sample: Pct }{ tasks: 5, sample: '100%' }A new member's first Tasks, all sampled.
goldenPct | falsefalseA share of Tasks with a known answer, scored only for quality.
contest{ by: 'peer' | 'staff'; within?: Within }'peer' where a row reserves the act, else 'staff'Who rules a contest of a member's work.
limits{ claims?; open?; declines?; late_releases? }noneThrottles and breakers, kept outside the track record.
firmsbooleannoneA firm may enroll a bench.
coverage{ per_act: number; by: 'pool' | 'member' | 'either'; usd?: number }noneInsurance in force per act, who carries it, and what it costs the brand per act, a ledger line.
contact'thread''thread'Member contact with a party relays through the job's thread: email and phone never cross either way.
fee{ usd: number; per: 'month' }noneThe one member charge allowed: a flat software fee, shown on the door.
invite_by_buyerbooleannoneA buyer may invite their own professional.
standby{ window: string; hours: number; retainer_usd: number }noneCommitted capacity: a dated window, hours a week and a weekly retainer.
grow{ waitlist?: true; referral_usd?: number; invites?: number }noneSupply growth: a waitlist, a referral bonus, invites per member.
publicbooleannonePublish the demand board on the door and in llms.txt.
export{ placement; to; min_pay?; notice?; first? }noneLet other brands import this Pool, each paying placement dollars per filled Task.
descriptorstring, with {member}the member's name, then VIA and your domainHow a member-rail charge reads on the buyer's statement.
payouts{ at: 'act'; after?: Within } | 'same-day' | 'weekly' | { schedule; hold_first?; instant_fee? }{ at: 'act', after: '2d' }When a member's pay is payable, and when payouts move it.
OpAPICLIMCPSDKSiteDocsApp
member.joinPOST /:portal/joinmembers add <pool> <email> k=v …never: a person actsnever: a person acts2 error pagesdelivery portal, owner console
member.verifyPOST /:portal/members/:id/verify2 error pagesthe verifier's write path, with its vt_ token
member.availabilityPOST /me/availabilityme availability <availability>never: a person actsnever: a person acts1 error pagedelivery portal
member.ledger.getGET /me/ledgerme ledgernever: a person actsnever: a person actsdelivery portal
member.ledger.addPOST /me/ledgerme ledger add <name> k=v …never: a person actsnever: a person acts1 error pagedelivery portal
member.ledger.checksGET /me/ledger/checksme ledger checksnever: a person actsnever: a person actsdelivery portal
member.missedGET /me/missedme missednever: a person actsnever: a person actsdelivery portal
member.earningsGET /me/earningsme earningsnever: a person actsnever: a person actsdelivery portal
member.trackGET /me/trackme tracknever: a person actsnever: a person actsdelivery portal
member.loginPOST /owner/members/:id/loginmembers login <member>never: a person actsnever: a person acts1 error pageowner console
task.listGET /tasks, GET /owner/taskstasks listnever: a person actsnever: a person actsdelivery portal, owner console
task.getGET /tasks/:id, GET /owner/tasks/:idtasks get <task>never: a person actsnever: a person acts2 error pagesdelivery portal, owner console

Objects: tsk_, mbr_. Events: member.applied, task.posted, task.unreached, task.offered, task.claimed, task.hold_expiring, task.extended, task.raised, task.widened, task.due_moved, task.declined, task.recused, task.contested, task.ruled, pay.accrued, pay.payable, pay.paid.

Operations has every column of every row.

Every member act is a person's own, in their session. No MCP tool or SDK method exists for one, and an agent that reaches one gets the link to hand on.

CodeCategoryHTTPWhy
ineligibleblocked403The member can't take this Task: the answer names the first eligibility predicate they fail, and its cure.
not_clearedblocked403The packet opens only to the member who claimed the Task and cleared it.
task_takenconflict409Another member holds this Task, or has it offered to them first.
defects_requiredinvalid_request422A send-back names its defects: what is wrong, for the step it returns to.
memo_requiredinvalid_request422A decline carries a reasoned memo, which only the client reads.
check gateWhy
pool_role_unknownA Service's role names a Pool that has no role by that name, so no member could ever be offered its Task.
reserved_act_unstaffedA step performs a reserved act, and no role that does it holds the credential its row reserves it to.
fill_bypasses_reservedA fill rung could offer a reserved act to someone who doesn't hold its credential.
rush_uncappedA role is paid from the buyer, its Pool's fill ladder raises the fee, and nothing caps what the buyer pays for it.
fill_underwaterThe Pool's fill_cap lets the ladder raise a brand-funded fee past what the job leaves: a job filled at the cap loses money.
sheet_without_consentA Pool's sheet names the job's parties to the members who read it, and the client hasn't consented before the first Task posts.
When the file won't loadThrown byWhy
rung_unparsedPool()Pool translators: fill[0] is 'raise 10% in 2h'. A rung is 'raise 10% 2h', 'widen 4h', 'firms 8h', 'staff 12h' or 'cancel 24h'.
rungs_out_of_orderPool()Pool translators: fill[1] 'raise 10% 2h' comes before 'widen 4h': rungs run in time order, so write them that way.

The legal example's Pool, as its file writes it:

captured/legal-check.txt · magic values: none
$ cat pools/attorneys.ts
import { Pool } from 'services.dev'

// One Pool, shared by every legal Service: attorneys join, the verifier checks each admission, and
// a cascade offers each Task one member at a time, because `check` names parties. Tiers are computed from outcomes,
// so the default fill ladder widens to the next one down. An attorney's own malpractice policy covers their act; one
// who carries none is covered by the Pool's, at $4 an act. A claim holds three times the step's estimate, at least a day.
export const attorneys = Pool({
  name: 'attorneys',
  roles: {
    attorney: { credential: 'bar admission' },
    practitioner: { credential: ['patent attorney', 'patent agent'], where: '*' },
    watcher: { credential: ['patent attorney', 'patent agent'], where: '*' },

The attorneys' Pool runs it end to end, and the preparers' Pool adds a season.

Whether a live Pool fills and pays, with an alarm and a control for each measure: Pool health.