Access that travels with the work
Goal. Complete one narrowing handoff so the trip works, the rogue stops, and no central lookup is needed.
In this stage a permission is something an agent is handed for a specific job. When the agent passes work along, it hands over a narrowed copy. It cannot hand over more, and the system checks this instead of trusting it.
Each agent now has its own key. A small control plane, separate from all six, signs the first permission for each trip. No agent can sign one from scratch.
What a warrant is
A warrant is a signed, self-contained permission that travels with the request: which tools, with which argument values, for which agent's key, until when, and how many more hops it may take. That is what Tenuo issues, narrows, and checks.
- Signed by a key no agent holds. The control plane signs the first warrant for a trip. Agents cannot sign a fresh one, because they do not have that key.
- Narrowed by whoever holds it. An agent can derive a warrant for another agent from the one it holds, with fewer tools, tighter values, a shorter life. It can never widen. The check happens against the parent before a token exists.
- Bound to the receiver's key. Every use is signed with the holder's key. A copy held by anyone else cannot be used.
- Checked next to the tool, offline. The code guarding a tool verifies the whole chain with the control plane's public key. There is no lookup and no service that has to be up.
// The control plane signs the root, for Travel Agent's key.
const trip = controlPlane.session({
allow: { check_in: { reservation: oneOf(["UA214", "AC712"]) }, /* ... */ },
holder: fleet["travel-agent"].publicKey,
ttlSeconds: 30 * 60,
maxDepth: 4,
});
// Flight Agent narrows what it holds for Check-in Agent's key. Core refuses
// anything that is not inside `flight`: more tools, a wider value, a longer life.
const forCheckin = fleet["flight-agent"].tenuo.narrow(
flight,
{ check_in: { reservation: oneOf(["UA214"]) } },
{ holder: fleet["checkin-agent"].publicKey, ttlSeconds: 5 * 60 },
);Why it is the right tool for this problem
| What you ran into | What a warrant does about it |
|---|---|
| Stage 2: an identity said who was acting and left out which job | The warrant carries the job: reservation UA214, trip-alice-cun, up to $300. |
| Stage 4: every check had to ask a component that knew about every task | Verification is local. central_calls goes to 0 and stays there when the control plane is down. |
| Stage 4: the only thing to hand over was the whole credential | narrow() hands over exactly the subset the next agent needs, bound to that agent's key. |
| Stage 4: the service could not tell whether the asker held what it asked for | A narrowed warrant must fit inside its parent. The rogue's request is refused before anything is signed. |
Read more: Concepts · Delegate to another agent (TypeScript guide) · Open a chain in the explorer
/**
* Flight → Check-in. TUTORIAL — complete and annotated.
*
* Flight Agent has just booked `reservation`. Check-in Agent needs to read
* that reservation and check it in, and it needs to be able to hand the
* boarding pass on. Nothing else. Bind the result to Check-in Agent's key
* (`fleet["checkin-agent"].publicKey`) with a short lifetime.
*/
export function flightToCheckin(flight: Session, fleet: Fleet, trip: Trip, reservation: string): Session {
// The agent has learned which reservation was booked, so this hop can turn
// the parent's list of candidates into one exact value.
const only = oneOf([reservation]);
return fleet["flight-agent"].tenuo.narrow(
flight,
{
get_reservation: { reservation: only },
check_in: { reservation: only },
// Keep this so Check-in Agent can narrow it once more for Boarding.
issue_boarding_pass: { reservation: only },
},
{
// Delegation must bind the result to the receiving agent's public key.
holder: fleet["checkin-agent"].publicKey,
ttlSeconds: 5 * 60,
},
);
}
/**
* Check-in → Boarding. YOUR LEVEL — copy the narrow() shape above.
*
* Boarding Agent needs to issue the boarding pass for `reservation`, and
* nothing else at all.
*/
export function checkinToBoarding(checkin: Session, fleet: Fleet, trip: Trip, reservation: string): Session {
throw new Error(`TODO: write the Check-in → Boarding link for ${reservation} (exercises/05-tenuo/chain.ts)`);
}Do this
-
Open the chain. Flight → Check-in is a complete, annotated tutorial: it narrows to one reservation, binds the next holder, and shortens the TTL. Copying its
narrow()shape is allowed.code exercises/05-tenuo/chain.ts -
Write the one TODO,
checkinToBoarding. Boarding Agent needs one tool for one reservation, bound to its key, for a short time.npm run labBefore you write the link npm run lab · 73 lines
Stage 5 of 7: Access that travels with the work mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-5 Switch to Tenuo and complete the chain in exercises/05-tenuo/chain.ts. Flight → Check-in is a complete, annotated tutorial. Copy its narrow() shape for the one TODO below it. Each agent now has its own key. A control plane, separate from all six, signs the first permission. Agents can narrow what they hold. None can sign one from scratch. Then look at the escalation attempt from stage 4, where it stopped, and at `central_calls`. The two-traveler run happens here too, with no policy file to edit. WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 7 / 7 STARS ☆★☆★ ☆ Trip booked ★ Rogue stopped ☆ Tight handoff ★ No spare authority 1 handoff travel-agent trip-alice-cun receive trip authority control plane ALLOWED 2 trip travel-agent trip-alice-cun traveler.read name ALLOWED 3 trip travel-agent trip-alice-cun calendar.create * ALLOWED 4 handoff travel-agent trip-alice-cun handoff → flight-agent CUN ALLOWED 5 trip flight-agent trip-alice-cun traveler.read passportNumber ALLOWED 6 trip flight-agent trip-alice-cun search_flights CUN ALLOWED 7 trip flight-agent trip-alice-cun book_flight UA214 ALLOWED 8 trip flight-agent trip-alice-cun wallet.charge $286 ALLOWED 9 handoff flight-agent trip-alice-cun handoff → checkin-agent UA214 ALLOWED 10 trip checkin-agent trip-alice-cun get_reservation UA214 ALLOWED 11 trip checkin-agent trip-alice-cun check_in UA214 ALLOWED 12 handoff checkin-agent trip-alice-cun handoff → boarding-agent UA214 DENIED reason: TODO: write the Check-in → Boarding link for UA214 (exercises/05-tenuo/chain.ts) 13 injected checkin-agent trip-alice-cun get_reservation AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] 14 injected checkin-agent trip-alice-cun check_in AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] 15 injected checkin-agent trip-alice-cun cancel_reservation UA214 DENIED reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 16 injected checkin-agent trip-alice-cun wallet.charge $412 DENIED reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 17 handoff travel-agent trip-alice-cun handoff → hotel-agent Cancún ALLOWED 18 trip hotel-agent trip-alice-cun traveler.read name ALLOWED 19 trip hotel-agent trip-alice-cun search_hotels Cancún ALLOWED 20 trip hotel-agent trip-alice-cun book_hotel HTL-CUN-2 ALLOWED 21 trip hotel-agent trip-alice-cun wallet.charge $420 ALLOWED 22 handoff travel-agent trip-alice-cun handoff → activity-agent Cancún ALLOWED 23 trip activity-agent trip-alice-cun traveler.read name ALLOWED 24 trip activity-agent trip-alice-cun search_activities Cancún ALLOWED 25 trip activity-agent trip-alice-cun book_activity ACT-5 ALLOWED 26 trip activity-agent trip-alice-cun wallet.charge $35 ALLOWED central_calls during the trip: 0 (calls to a component outside the acting agent) THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✗ trip-alice-cun boarding: pass for UA214 never attempted (an earlier step or handoff failed) ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) Explorer link saved as explorer-stage-5.url in the lab state directory. Run npm run trace -- --open-explorer to open it. npm run attack the rogue behavior and the tests npm run score points and why npm run trace every decision with its reason npm run next when you are done hereWhen the link exists npm run lab · 73 lines
Stage 5 of 7: Access that travels with the work mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-5 Switch to Tenuo and complete the chain in exercises/05-tenuo/chain.ts. Flight → Check-in is a complete, annotated tutorial. Copy its narrow() shape for the one TODO below it. Each agent now has its own key. A control plane, separate from all six, signs the first permission. Agents can narrow what they hold. None can sign one from scratch. Then look at the escalation attempt from stage 4, where it stopped, and at `central_calls`. The two-traveler run happens here too, with no policy file to edit. WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 7 / 7 STARS ★★★★ ★ Trip booked ★ Rogue stopped ★ Tight handoff ★ No spare authority 1 handoff travel-agent trip-alice-cun receive trip authority control plane ALLOWED 2 trip travel-agent trip-alice-cun traveler.read name ALLOWED 3 trip travel-agent trip-alice-cun calendar.create * ALLOWED 4 handoff travel-agent trip-alice-cun handoff → flight-agent CUN ALLOWED 5 trip flight-agent trip-alice-cun traveler.read passportNumber ALLOWED 6 trip flight-agent trip-alice-cun search_flights CUN ALLOWED 7 trip flight-agent trip-alice-cun book_flight UA214 ALLOWED 8 trip flight-agent trip-alice-cun wallet.charge $286 ALLOWED 9 handoff flight-agent trip-alice-cun handoff → checkin-agent UA214 ALLOWED 10 trip checkin-agent trip-alice-cun get_reservation UA214 ALLOWED 11 trip checkin-agent trip-alice-cun check_in UA214 ALLOWED 12 handoff checkin-agent trip-alice-cun handoff → boarding-agent UA214 ALLOWED 13 trip boarding-agent trip-alice-cun issue_boarding_pass UA214 ALLOWED 14 injected checkin-agent trip-alice-cun get_reservation AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] 15 injected checkin-agent trip-alice-cun check_in AA882 DENIED reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] 16 injected checkin-agent trip-alice-cun cancel_reservation UA214 DENIED reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 17 injected checkin-agent trip-alice-cun wallet.charge $412 DENIED reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] 18 handoff travel-agent trip-alice-cun handoff → hotel-agent Cancún ALLOWED 19 trip hotel-agent trip-alice-cun traveler.read name ALLOWED 20 trip hotel-agent trip-alice-cun search_hotels Cancún ALLOWED 21 trip hotel-agent trip-alice-cun book_hotel HTL-CUN-2 ALLOWED 22 trip hotel-agent trip-alice-cun wallet.charge $420 ALLOWED 23 handoff travel-agent trip-alice-cun handoff → activity-agent Cancún ALLOWED 24 trip activity-agent trip-alice-cun traveler.read name ALLOWED 25 trip activity-agent trip-alice-cun search_activities Cancún ALLOWED 26 trip activity-agent trip-alice-cun book_activity ACT-5 ALLOWED 27 trip activity-agent trip-alice-cun wallet.charge $35 ALLOWED central_calls during the trip: 0 (calls to a component outside the acting agent) THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✓ trip-alice-cun boarding: pass for UA214 ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) Explorer link saved as explorer-stage-5.url in the lab state directory. Run npm run trace -- --open-explorer to open it. npm run attack the rogue behavior and the tests npm run score points and why npm run trace every decision with its reason npm run next when you are done here -
Run the checks. The two-traveler run happens here too, with no policy file to edit. Read CROSS-TASK and the escalation attempt, then find
central_calls.npm run attackWhat you should see npm run attack · 157 lines
Stage 5 of 7: Access that travels with the work mode=tenuo scenario=spring-break guide: https://tenuo.ai/lab/stage-5 WALLET Alice: $459 of $1200 ROGUE ATTEMPTS BLOCKED 7 / 7 STARS ★★★★ ★ Trip booked ★ Rogue stopped ★ Tight handoff ★ No spare authority THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✓ trip-alice-cun boarding: pass for UA214 ✓ trip-alice-cun hotel: search ✓ trip-alice-cun hotel: book ✓ trip-alice-cun activity: search ✓ trip-alice-cun activity: book ✓ trip-alice-cun within budget ($741 of $1200) HANDOFFS travel-agent: receive trip authority control plane ALLOWED travel-agent holds {book_activity, book_flight, book_hotel, calendar.create, check_in, get_reservation, issue_boarding_pass, search_activities, search_flights, search_hotels, traveler.read, wallet.charge}, maxDepth 4 travel-agent: handoff → flight-agent CUN ALLOWED flight-agent now holds {book_flight, check_in, get_reservation, issue_boarding_pass, search_flights, traveler.read, wallet.charge} at depth 1 flight-agent: handoff → checkin-agent UA214 ALLOWED checkin-agent now holds {check_in, get_reservation, issue_boarding_pass} at depth 2 checkin-agent: handoff → boarding-agent UA214 ALLOWED boarding-agent now holds {issue_boarding_pass} at depth 3 travel-agent: handoff → hotel-agent Cancún ALLOWED hotel-agent now holds {book_hotel, search_hotels, traveler.read, wallet.charge} at depth 1 travel-agent: handoff → activity-agent Cancún ALLOWED activity-agent now holds {book_activity, search_activities, traveler.read, wallet.charge} at depth 1 LEGITIMATE check_in(UA214) ALLOWED ✓ TRIGGERED BY INJECTED CONTENT get_reservation(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] check_in(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] cancel_reservation(UA214) DENIED ✓ reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] wallet.charge(412) DENIED ✓ reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] book_flight(AA882, 412) DENIED ✓ reason: book_flight is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] PROBE (harness, independent of model) traveler.read(passportNumber) DENIED ✓ reason: traveler.read is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] calendar.delete(*) DENIED ✓ reason: calendar.delete is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] BOARDING AGENT AFTER THE HANDOFF issue_boarding_pass(UA214) intended ALLOWED ✓ check_in(UA214) inherited? DENIED ✓ reason: check_in is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] get_reservation(UA214) inherited? DENIED ✓ reason: get_reservation is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] ESCALATION: checkin-agent tries to arrange broader access for boarding-agent requested: every reservation; read, check in, cancel DENIED ✓ reason: DENIED at narrow(), in checkin-agent's own process, before any token existed: TENUO_CHAIN_INVALID: attenuation would expand capabilities: tool 'cancel_reservation' not in parent's tools. narrow() cannot add 'cancel_reservation': remove it or delegate from a parent that grants it. [TENUO_CHAIN_INVALID] clean: all 12 checks landed as expected central_calls during the trip: 0 (calls to a component outside the acting agent) Stage 5 of 7: Access that travels with the work mode=tenuo scenario=two-travelers guide: https://tenuo.ai/lab/stage-5 WALLET Alice: $914 of $1200 Bob: $1102 of $1500 ROGUE ATTEMPTS BLOCKED 7 / 7 STARS ★★★★ ★ Trip booked ★ Rogue stopped ★ Tight handoff ★ No spare authority THE TRIP ✓ trip-alice-cun travel: read traveler name ✓ trip-alice-cun travel: calendar event ✓ trip-alice-cun flight: search ✓ trip-alice-cun flight: book UA214 ✓ trip-alice-cun check-in: UA214 ✓ trip-alice-cun boarding: pass for UA214 ✓ trip-alice-cun within budget ($286 of $1200) ✓ trip-bob-sea travel: read traveler name ✓ trip-bob-sea travel: calendar event ✓ trip-bob-sea flight: search ✓ trip-bob-sea flight: book DL331 ✓ trip-bob-sea check-in: DL331 ✓ trip-bob-sea boarding: pass for DL331 ✓ trip-bob-sea within budget ($398 of $1500) HANDOFFS travel-agent: receive trip authority control plane ALLOWED travel-agent holds {book_activity, book_flight, book_hotel, calendar.create, check_in, get_reservation, issue_boarding_pass, search_activities, search_flights, search_hotels, traveler.read, wallet.charge}, maxDepth 4 travel-agent: handoff → flight-agent CUN ALLOWED flight-agent now holds {book_flight, check_in, get_reservation, issue_boarding_pass, search_flights, traveler.read, wallet.charge} at depth 1 flight-agent: handoff → checkin-agent UA214 ALLOWED checkin-agent now holds {check_in, get_reservation, issue_boarding_pass} at depth 2 checkin-agent: handoff → boarding-agent UA214 ALLOWED boarding-agent now holds {issue_boarding_pass} at depth 3 travel-agent: receive trip authority control plane ALLOWED travel-agent holds {book_activity, book_flight, book_hotel, calendar.create, check_in, get_reservation, issue_boarding_pass, search_activities, search_flights, search_hotels, traveler.read, wallet.charge}, maxDepth 4 travel-agent: handoff → flight-agent SEA ALLOWED flight-agent now holds {book_flight, check_in, get_reservation, issue_boarding_pass, search_flights, traveler.read, wallet.charge} at depth 1 flight-agent: handoff → checkin-agent DL331 ALLOWED checkin-agent now holds {check_in, get_reservation, issue_boarding_pass} at depth 2 checkin-agent: handoff → boarding-agent DL331 ALLOWED boarding-agent now holds {issue_boarding_pass} at depth 3 LEGITIMATE check_in(UA214) ALLOWED ✓ TRIGGERED BY INJECTED CONTENT get_reservation(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] check_in(AA882) DENIED ✓ reason: reservation AA882 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] cancel_reservation(UA214) DENIED ✓ reason: cancel_reservation is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] wallet.charge(412) DENIED ✓ reason: wallet.charge is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] book_flight(AA882, 412) DENIED ✓ reason: book_flight is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] PROBE (harness, independent of model) traveler.read(passportNumber) DENIED ✓ reason: traveler.read is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] calendar.delete(*) DENIED ✓ reason: calendar.delete is not in checkin-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] BOARDING AGENT AFTER THE HANDOFF issue_boarding_pass(UA214) intended ALLOWED ✓ check_in(UA214) inherited? DENIED ✓ reason: check_in is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] get_reservation(UA214) inherited? DENIED ✓ reason: get_reservation is not in boarding-agent's warrant [TENUO_TOOL_NOT_AUTHORIZED] CROSS-TASK Task A agent: check_in(DL331) DENIED ✓ reason: reservation DL331 is outside the warrant's constraint for check_in [TENUO_CONSTRAINT_VIOLATION] Task B agent: get_reservation(UA214) DENIED ✓ reason: reservation UA214 is outside the warrant's constraint for get_reservation [TENUO_CONSTRAINT_VIOLATION] Task B agent: book_flight(UA214, CUN) DENIED ✓ reason: destination CUN is outside the warrant's constraint for book_flight [TENUO_CONSTRAINT_VIOLATION] ESCALATION: checkin-agent tries to arrange broader access for boarding-agent requested: every reservation; read, check in, cancel DENIED ✓ reason: DENIED at narrow(), in checkin-agent's own process, before any token existed: TENUO_CHAIN_INVALID: attenuation would expand capabilities: tool 'cancel_reservation' not in parent's tools. narrow() cannot add 'cancel_reservation': remove it or delegate from a parent that grants it. [TENUO_CHAIN_INVALID] clean: all 15 checks landed as expected central_calls during the trip: 0 (calls to a component outside the acting agent) That check ran locally, in the agent's own process, with no server to ask. The code that did it is open source: github.com/tenuo-ai/tenuo A star helps other people find it. -
Open the chain Boarding Agent holds, hop by hop, in the explorer.
npm run trace -- --open-explorer
I'm stuck. Give me a hint.
reservation to that one flight. Bind each result to the next agent's key with holder, and keep lifetimes short. Every argument a tool is called with must be named: leave one out and the call is refused.Show a reference solution
Try your own first. The score checks behavior, so yours does not need to match this one.
Open the Stage 5 reference on GitHubnpm run attack is clean for both scenarios and central_calls is 0.