Cart
def Cart(
*args, **kwargs
):One shopping session: find things, put them in, read the trolley back.
fossick.shop behind a small interface, and the weekly grocery run it was written for.
This notebook lets the agent shop: search a store, put products in the trolley and read the trolley back. It is written around one real job, a weekly grocery order split across two Australian stores, and it shows how to add a whole new kind of capability to Ramabana without touching the harness.
The host is the agent’s connection to the outside world, and every host capability turns into tools for every agent built on it. Shopping does not belong there. An agent editing a repository needs no cart, and most hosts have no browser to run one. So the cart arrives as an extension instead: a Python file in the config folder whose setup(reg) function registers the cart’s tools with reg.tool. Without that file there is no cart.
Cart is the interface every cart implements: open a store, find products, add one, and read back the lines and the total. FossickCart drives a real logged-in Chrome. FakeCart is an in-memory stand-in. The tests and the worked example below use FakeCart, so building the docs never fills a real trolley. A failed cart action raises CartError.
One shopping session: find things, put them in, read the trolley back.
A cart action failed: no such product, no add control, nothing matched.
FossickCart drives a logged-in Chrome tab through fossick.shop. It starts the browser session on first use, on the debugging port port, and reuses it after that.
fossick.shop does the hard part:
/cart/add.js directlyFossickCart is a thin adapter from the Cart methods to those calls. stores() lists the stores fossick knows the search and trolley pages of, from its SITES table. Both stores in the example are in that table, so their urls are known rather than guessed. Coles blocks bots that come from datacentre addresses and Ceres is a members’ shop, so the cart drives a browser you have logged into instead of fetching pages.
Real Chrome trolley via fossick.shop, opened on first use and reused.
FakeCart is a shop with a fixed catalogue, CATALOGUE unless you pass one, and a list for a trolley. It picks the store from the catalogue by matching the current url. It matches products the way fossick does. A number means that row of the last search. A title is matched exactly first, then as a substring, against the last search and then the whole store, and the shortest matching title wins. When nothing matches it raises CartError listing what the store stocks, rather than guess. A model can recover from “no product matching ‘caviar’” followed by a list of titles. It cannot recover from a silent wrong add.
The cells below search for milk, add two of it and a loaf of sourdough, and read the total.
A browserless cart with a fixed catalogue, for tests and docs.
A failed match names the options instead of guessing, so the model can try again instead of buying the wrong thing.
cart_tools(cart) turns any Cart into six tools for the model: cart_stores, cart_open, cart_find, cart_add, cart_show and cart_remove. cart_add reports added only when the trolley changed, DID NOT ADD when it did not, and UNVERIFIED when the cart could not tell.
Registering them takes one extension file in the config folder. Nothing in the harness changes:
The cell below does the same with a Registry and a FakeCart.
['cart_stores',
'cart_open',
'cart_find',
'cart_add',
'cart_show',
'cart_remove']
cart_add and cart_remove are in WRITE_TOOLS, the set of tools the approval gate guards, so an Approvals gate asks before either runs, as it would before a file write. Spending money deserves a confirmation at least as much.
This is the whole job end to end: a reminder set once, noticed later, and acted on.
Only the model’s decisions and the trolley are fakes. The vault, the watch and the poll are real, and every tool call below runs the actual tool. ScriptedBackend, from the testing notebook, chooses which tool to call and streams the words, and the harness does everything else. The host is a VaultHost over a temporary vault, with no_model as its graph chat so no model loads.
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
<ramabana.vault.VaultHost at 0x7f31a5cf6270>
Once, weeks ago. Store the shopping list as a note and set a weekly reminder against it. start is when the reminder first comes due. Setting it a second in the past makes the rest of the page the morning the reminder fires, with no fake clock.
host.remember('Weekly Coles order: milk, sourdough, eggs, baby spinach, olive oil. '
'Plus a seasonal fruit box from Ceres Fair Food.',
title='the weekly shop', tags=['groceries'])
due = host.watch('Put the weekly groceries in the Coles trolley, and add a Ceres fruit box.',
action='remind', every='1w', note='weekly shop', start=time.time() - 1)
int(due['every'])No one has polled yet, so the vault shows the reminder as due.
The morning it fires. The agent gets the host’s own tools plus the cart’s. gpt-mini resolves to openai/gpt-5.6-luna, fast and cheap, which suits a run of many small steps that each can be checked. The script never calls it: the steps below are played by ScriptedBackend.
script = [
Step('There is a weekly shop. Pulling the list.',
tool=('memory_search', {'query': 'weekly Coles order list', 'limit': 2})),
Step('Opening Coles.', tool=('cart_open', {'url': 'https://www.coles.com.au'})),
*[s for item in ['milk', 'sourdough', 'eggs', 'baby spinach', 'olive oil']
for s in (Step(f'Looking for {item}.', tool=('cart_find', {'query': item})),
Step(f'Adding {item}.', tool=('cart_add', {'item': item})))],
Step('Now the fruit box.', tool=('cart_open', {'url': 'https://members.ceresfairfood.org.au'})),
Step('', tool=('cart_find', {'query': 'fruit box'})),
Step('', tool=('cart_add', {'item': 'Seasonal Fruit Box - Medium'})),
Step('Reading the trolley back.', tool=('cart_show', {})),
Step('Done: five staples in the Coles trolley and a medium seasonal fruit box from Ceres.'),
]
be = ScriptedBackend(steps=script, token_delay=0)
be.refresh('', list(tools.values()))
agent.poll_watches(force=True).join()
print(be.send('anything outstanding this week?'))The harness polls, not the model: there is no poll_watches tool. In a session Agent.poll_watches runs at the start of each turn. Here the script is sent to the backend directly, so the cell calls it by hand first. The reminder that fired became a vault note, and the first memory_search finds it:
Check the trolley, not the transcript. cart_add compares the trolley before and after each add, so a reported add is one the cart confirmed.
The reminder has rescheduled itself for next week, so the job repeats with no new setup.
The same run on a browser you are already logged into. The cart is FossickCart, and the agent asks before every write:
from ramabana import Agent
from ramabana.agent import Approvals
from ramabana.shop import FossickCart, cart_tools
from ramabana.tools import WRITE_TOOLS
from ramabana.vault import VaultHost
host = VaultHost(roots=['~/notes']) # the shared ~/.vishalakshi/vault.db
agent = Agent(host, model='gpt-mini', approvals=Approvals(WRITE_TOOLS, mode='ask'))
for t in cart_tools(FossickCart()): agent.add_tool(t)
agent.ask('anything outstanding this week?')Two rules for a real trolley. Keep the approval gate on: cart_add is in WRITE_TOOLS, so you see every add before it happens. And leave the checkout alone. fossick.shop has shop_fill for the payment form, and this module does not wrap it. A trolley can be emptied again. A placed order cannot.