Add a signer
A sworn translator from your Pool signs every job. A rules row reserves the act to their credential, and the signature is stamped into the file it binds.
Some work is finished only when a credentialed person signs it. A Service meets that by design: the signer is a role filled from your Pool, a rules row reserves signing to their credential, and the gate commits the signature, bound to the exact file. AI still does the work. This is Grow 3.
Three things join Translation: a role, the Pool that fills it, and one row of your rules table.
import { Pool, Rules, Service } from 'services.dev'
// Grow 3, add a signer: a sworn translator from lingua's Pool signs every job before delivery, and the
// stamp writes the member, their credential and the date into the file the signature binds. The rules
// row reserves signing to that credential, so only a verified member can take the step. AI does the work.
export default Service({
name: 'Translation',
description: 'A document, translated and signed.',
input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
output: { document: 'file' },
rules: rules(),
roles: { translator: { pool: translators(), pay: 30 } },
deliver: {
glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },
translate: 'ai',
harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
sign: { by: 'translator', stamp: true, est: '20min' },
},
price: 89,
})| Key | What it does |
|---|---|
roles.translator | Who takes the sign step: filled from pool, and paid pay dollars an act, posted on the Task. A role with no pool is your own staff |
Pool({ roles, join, verify }) | Your marketplace: the credential each role needs, what a person sends to join, and the verifier, the only writer of that credential |
Rules({ version, services }) | Your table. This row reserves sign to a sworn translator, and position records why |
sign.stamp | Writes the member, their credential and the date into the file before the signature hashes it |
sign.est | How long signing takes, so an offer shows its rate |
Here the Pool and the table sit below the Service. In a brand they're files of their own, pools/ and rules.ts, and either can ship as a package several brands import. The row is illustrative; your counsel writes the real one (law.org.ai).
npx services.dev checkservices/translation.ts · 13 lines · $89 per job, within 48 hours, charged on delivery.
who A translator signs every document before delivery. (rules 2026-10) The buyer answers at glossary.
roles translator · sworn translator · from sworn-translators · $30 an act
steps glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses → sign · translator · * · stamped
bounds reserved sign to sworn translator
agent order_translation · 25 of 30 words · job_7Hq2 waits on party
ledger +$89.00 · AI $1.26 (example) · translator $30.00 · you $57.74
margin at least $41.20 when the first translator signs ($89.00 − $30.00 translator − $17.80 AI cap) · $26.20 at the ladder's cap, the lower of 50% and what the floor leaves · $11.20 after a decline and a second translator
a job that stops costs you at most $47.80
apps customer portal · delivery portal · owner console
mcp 13 buyer tools · 4,490 of 8,000 tokens · 9 more with ?toolset=sandbox
✓ ready to sell · 1 Service · it keeps at least 46% of every job
next npx services.dev dev
AI and money are simulated until you wire them. $0.42 a call is an example.bounds is the rules row. margin prices each way the signature goes: the first translator, the fill ladder's cap, or a second translator after a decline. Give the role another credential and check refuses with reserved_act_unstaffed:
npx services.dev checkservices/translation.ts · 13 lines · $89 per job, within 48 hours, charged on delivery.
who A translator signs every document before delivery. (rules 2026-10) The buyer answers at glossary.
roles translator · certified translator · from sworn-translators · $30 an act
steps glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses → sign · translator · * · stamped
bounds reserved sign to sworn translator
agent order_translation · 25 of 30 words · job_7Hq2 waits on party
ledger +$89.00 · AI $1.26 (example) · translator $30.00 · you $57.74
margin at least $41.20 when the first translator signs ($89.00 − $30.00 translator − $17.80 AI cap) · $26.20 at the ladder's cap, the lower of 50% and what the floor leaves · $11.20 after a decline and a second translator
a job that stops costs you at most $47.80
apps customer portal · delivery portal · owner console
✗ A step performs a reserved act, and no role that does it holds the credential its row reserves it to. Give the step to a role that holds the credential, or set the role's credential to one that meets it. sign performs sign, reserved to sworn translator (rules 2026-10), and the translator role holds only certified translator.
at services/translation.ts:26 · roles.translator.credential
https://services.dev/docs/reference/check/reserved_act_unstaffed
mcp 13 buyer tools · 4,490 of 8,000 tokens · 9 more with ?toolset=sandbox
✗ 1 to fix
next Give the step to a role that holds the credential, or set the role's credential to one that meets it. sign performs sign, reserved to sworn translator (rules 2026-10), and the translator role holds only certified translator.
AI and money are simulated until you wire them. $0.42 a call is an example.A sworn translator joins first. On localhost a fixture verifier writes their credential, marked simulated:
→ POST /sworn-translators/join {"email":"ines@example.com","answers":{"name":"Inès Martin","credential":"sworn translator","languages":["fr","en"]}}← 200 mbr_7Hq2 · sworn-translators · ines@example.com · verified (simulated): sworn translator in * · onThe job waits on the Task until the translator signs, then the buyer's next wait answers delivered:
npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 89 --key growjob_7Hq2 · working · $89.00 held
job_7Hq2 · waiting · on party · $89.00 held
record
next hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · waiting · on claim · $89.00 held
record glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass
next a member claims tsk_7Hq2: npx services.dev tasks sheet tsk_7Hq2, in their session
# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest
$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 chargedcurl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89}' next get_job {"id":"job_7Hq2"}
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on party · $89.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on claim · $89.00 held
next get_job {"id":"job_7Hq2"}
# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest
$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89}← job_7Hq2 · working · $89.00 held
next get_job {"id":"job_7Hq2"}
→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on party · $89.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on claim · $89.00 held
next get_job {"id":"job_7Hq2"}
# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest
→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 chargedawait lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89})← job_7Hq2 · working · $89.00 held
next get_job {"id":"job_7Hq2"}
await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on party · $89.00 held
next hand_to_person {"ask":"ask_7Hq2"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}
await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on claim · $89.00 held
next get_job {"id":"job_7Hq2"}
# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest
await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 chargedYou own the brand, and you still can't sign:
→ POST /sworn-translators/jobs/job_7Hq2/approve← 403 needs_a_person: sign is tsk_7Hq2, a Task for a member of sworn-translators: the member who claims and clears it attests, in their own session.Only the member who claims the Task attests, in their own session:
→ POST /tasks/tsk_7Hq2/claim← tsk_7Hq2 · claimed · translator · sign · * · $30.00 · 20 min · $90.00 an hour · due 2026-10-27 16:02
→ POST /tasks/tsk_7Hq2/clear · GET /tasks/tsk_7Hq2/packet
→ POST /tasks/tsk_7Hq2/attest
← tsk_7Hq2 · done · translator · sign · * · $30.00 · attested · 20 min · $90.00 an hour · due 2026-10-27 16:02
← job_7Hq2 · delivered · lease.fr-CA.pdf · $89.00 charged
record glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · person · sworn translator · * · mbr_7Hq2
ledger +$89.00 · AI $1.26 · translator $30.00 · you $57.74
signed {"member":"mbr_7Hq2","credential":"sworn translator","region":"*","rules":"2026-10","fields":["document"],"hash":"sha256:7fc0ab6fb17cb034de1bdb37a90b2e59a9dd4191277212ea85890e001962353f"}
stamp Signed by mbr_7Hq2, sworn translator, 2026-10-25.signed is what the gate committed: who, under which credential and rules version, and a hash of the file as signed. A later change voids it. The gate commits AI and people the same way.
npx services.dev diff grow/ask.ts grow/role.tsgrow/ask.ts (9 lines) → grow/role.ts (13 lines)
Translation · changed description, deliver, human, price, portal, roles, rules · adds a person: the human line, or a step a role does; a role from a Pool: its members, and the Tasks they claim
API ~ operation service.order.translation POST /translation item.description
~ operation service.quote.translation GET /translation.json answers.description, answers.deliver, answers.human.sentence
+ operation job.approve POST /{portal}/jobs/{id}/approve
+ operation member.join POST /{portal}/join
+ operation member.verify POST /{portal}/members/{id}/verify
+ operation member.availability POST /me/availability
+ operation member.ledger.get GET /me/ledger
+ operation member.ledger.add POST /me/ledger
+ operation member.ledger.checks GET /me/ledger/checks
+ operation member.missed GET /me/missed
+ operation task.list GET /tasks
+ operation task.get GET /tasks/{id}
+ operation task.sheet GET /tasks/{id}/sheet
+ operation task.pass POST /tasks/{id}/pass
+ operation task.claim POST /tasks/{id}/claim
+ operation task.clear POST /tasks/{id}/clear
+ operation task.packet GET /tasks/{id}/packet
A Pool brings its own surface: joining and every Task act on the API and CLI, and the delivery portal in the App. A person acts in their own session, so none of it is an MCP tool.
The tutorial goes on to File it somewhere. On the licensed path, read human: the line, then rules and roles.