AI Agent

An operator, not a chatbot

Agent panel Replay

Create a meaningful description in English for product "DRILL001"

Done

-> GetProductBasic(main_id="DRILL001") -> 1 match

-> GetProductExtra(product_id=4182, tabs=["CORE", "TECH"]) -> 38 values

Everything a paragraph needs is already on the record: 18 V brushless, 13 mm metal chuck, 60 Nm, two speeds, 1.7 kg with the 2.0 Ah pack. Your other descriptions set the tone:

Brushless power in a drill you can hold all day. 60 Nm through a two-speed gearbox and a 13 mm metal chuck, so masonry, timber and steel are the same job with a different bit.

No claim in it that is not on the record. description_en is empty, so this is a fill, not an overwrite.

Write this to description_en on DRILL001?

Write it Let me edit it first

your answer

-> UpdateProducts(ids=[4182], changes={"description_en": "…"}) -> 1 updated, 0 errors

Written, and on the product's history as an agent edit.

11s · 6.4k in / 512 out · 3 rounds · 3 tools

Agent panel Replay

Read the whole catalog and propose a category tree. Do not create anything yet.

Waiting for you

-> GetDashboardStats() -> 4,812 products · 148 fields · 0 categories

-> GetListProducts(offset=0, limit=200) -> 200 rows (× 25 pages)

Six families carry the catalog, and product_group plus voltage decide the third level. 41 nodes, three deep:

Power tools / Drills / Cordless   1,204
Power tools / Drills / Corded       318
Power tools / Saws / Circular        402
Power tools / Grinders              611
Hand tools / Wrenches               488

1,204 products carry nothing that decides Cordless from Corded. I would leave those on Drills rather than guess.

Create the 41 categories and assign the 3,608 unambiguous products?

Create the tree Show me the assignments first

Running · 1m 12s · 61k in / 3.4k out · 7 rounds · 27 tools

Agent panel Replay

Every product from supplier NORTHFIELD moves to the POWER tab, torque in Nm, and prefix the main id with NF-

Done

-> GetListProducts(filter={"supplier": "NORTHFIELD"}) -> 412 rows

-> GetFields(codes=["torque", "main_id"]) -> 2 definitions

412 products. torque is a NUMBER field and 388 are in lb-ft, so those get × 1.3558 and a new unit; 24 already read Nm. Three main ids already start with NF-.

Apply to 412 products — tab POWER, torque → Nm (388 converted), main id prefixed NF- (409)?

Apply Cancel

your answer

-> UpdateProducts(ids=[…412 ids…], changes={"tab": "POWER", "torque": …, "main_id": …}) -> 412 updated, 0 errors

412 in one batch, one entry in the audit trail. If the conversion was wrong it is one undo, not four hundred.

24s · 18.2k in / 940 out · 4 rounds · 3 tools

Agent panel Replay

Import supplier-q3.xlsx

Done

-> RunListSheets(file="supplier-q3.xlsx") -> 4 sheets

Which sheet holds the products?

Products · 1,914 rows Pricing Legende Changelog

your answer

-> RunAnalyzeImport(file_id=1207, sheet="Products") -> shape=one_x_row

One product per row. 41 columns land on existing fields, 9 are dimensions, 5 are image filenames, 6 are new — those go on an IMPORT tab.

Dry run: 1,742 updates, 172 new products, 6 new fields. Import it?

Import Change the mapping

your answer

-> RunImport(mapping_id=88, file_id=1207) -> 172 new · 1,742 updated · 0 errors

The mapping is bound to this file's checksum, so next quarter's workbook gets checked, not assumed.

2m 40s · 88k in / 5.1k out · 9 rounds · 6 tools

Agent panel Replay

Build the export mapping for the Grainger template. Their notes are on the channel.

Done

-> ListChannels() -> 6 channels

-> GetChannelHelperJson(channel_id=49) -> 74 column notes

-> GetExportDslReference()

74 columns. 58 read straight from a field. 11 need a transform — their Pack Qty wants an integer where you keep a string, and Long Description is capped at 4,000 characters. 3 are conditional: Hazmat Code only ships when un_number is set. 2 I cannot source at all: UNSPSC and Country of Origin do not exist in your schema.

-> SaveExportMapping(channel_id=49, columns=74, unsourced=2)

-> RunExport(channel_id=49, preview=true) -> 50 rows

Preview is on the Channels page. The two unsourced columns export empty until the fields exist — say so and I will create them and come back to this mapping.

1m 05s · 44k in / 7.8k out · 6 rounds · 5 tools

Agent panel Replay

CORE completeness is 71%. Work the list until it is not.

Done

-> GetProductCompleteness(tab="CORE") -> 1,383 products below 100%

-> GetListProducts(filter={"missing": "CORE"}, limit=200) -> 200 rows (× 7 pages)

It is three fields, not a thousand products: weight missing on 1,190, country_of_origin on 612, hs_code on 415. For this demo, 508 of them have confirmed country and HS code values in the source data — not derived from brand and factory alone, and nothing invented. Weight is on the supplier PDFs: a different job.

Write country_of_origin and hs_code on the 508 the source data confirms?

Write them Show me twenty first

your answer

-> UpdateProducts(ids=[…508 ids…], changes={"country_of_origin": …, "hs_code": …}) -> 508 updated, 0 errors

CORE completeness 71% → 88%. The remaining 875 are in core-gaps.csv, grouped by what is missing. Nothing confirms those, so I stopped.

3m 18s · 121k in / 4.2k out · 11 rounds · 10 tools

Agent panel Replay
Spoken, ended with "go"

Move everything in the Discontinued category to inactive… go

Done

-> GetListCategories(cat_name="Discontinued") -> 1 match(es)

-> GetListProducts(cat_id=77) -> 306 rows

You ended with "go", so I did not wait for you to press send. The write still stopped here, because 306 products at once is wide-reaching whether the instruction was typed or spoken.

Set 306 products to status inactive?

Yes No

your answer

-> UpdateProducts(ids=[…306 ids…], changes={"status": "inactive"}) -> 306 updated, 0 errors

306 products inactive. They stay in every export that asks for inactive rows and drop out of the ones that do not.

19s · 9.7k in / 380 out · 3 rounds · 3 tools

Agent panel Replay

Every working day at 9:00 give me a list of the products created since the previous list.

Done

That is a standing job, not a question, so I am saving it as a workflow rather than answering it once. Two things the sentence does not settle: whose 9:00, and where the list should land.

Mon–Fri at 09:00 Europe/Madrid — deliver the list where?

In the agent panel By email as well

your answer

-> SaveWorkflow(name="New products since last run", cron="0 9 * * 1-5", tz="Europe/Madrid") -> workflow #12 armed

-> RunWorkflow(id=12, preview=true) -> GetListProducts(filter={"created_since": "last_run"}) -> 14 rows

DRILL-8842   Impact driver 18 V       17:41  import
NF-2201      Angle grinder 125 mm     17:52  import
HT-0916      Adjustable wrench 250 mm 09:04  by hand
…and 11 more

Monday's run covers Friday, Saturday and Sunday — a working-day schedule that skipped the weekend's products would never report them at all.

This one reads and reports; it writes nothing. A workflow that did write would stop at the same gate as any other run and wait for you, which is why one cannot quietly change the catalog at nine in the morning.

12s · 5.8k in / 420 out · 3 rounds · 3 tools

Agent panel Replay

Create an email to Bob with the spec sheet for product ABC, and attach the full documentation.

Done

-> GetProductBasic(main_id="ABC") -> 1 match

-> GetProductExtra(product_id=915, tabs=["CORE", "TECH"]) -> 44 values

-> GetAssets(product_id=915, roles=["doc"]) -> 3 files (2.7 MB)

Spec sheet built from the TECH tab as a table in the body rather than a fourth attachment, since that is the part he will read on a phone. The three documents are the datasheet, the declaration of conformity and the maintenance manual.

-> CreateGmailDraft(to="[email protected]", attachments=3) -> draft created

It is a draft in your own mailbox, not a sent message. Nothing here can put mail on the wire without you pressing send.

28s · 14.1k in / 1.1k out · 4 rounds · 4 tools

Agent panel Replay

Send an email to Marketing with the products that lack a Description.

Done

-> GetUsers(group="Marketing") -> 4 users

"Marketing" is a group in your own user list, so this goes to four people who already hold accounts on this catalog. There is no argument on this tool for an address that is not one of yours.

-> GetListProducts(filter={"missing": "description_en"}, limit=500) -> 318 rows

318 products with an empty description_en. 47 are discontinued and nobody is going to write copy for those, so they are listed apart rather than padding the number someone reads first.

Send to the 4 people in Marketing, 271 products to write plus 47 discontinued?

Send it Show me the mail first

your answer

-> SendUserEmail(group="Marketing", subject="271 products still need a description", attachments=1) -> sent to 4

Sent, with the recipient list on the run's record. Next week the same instruction is one line on a schedule instead of a request.

26s · 12.6k in / 780 out · 4 rounds · 3 tools

Agent panel Replay

Run a command to see conversation history here.

Pick a job on the left and it runs here.

How it asks

When it is unsure, it asks you — in the app

The agent does not guess when a decision needs you. It asks in the interface: which sheet to use, which column identifies the product, whether two fields are the same, or whether a change should really delete hundreds of products. You answer, and the run continues. That keeps autonomous work controlled and reviewable.

  • Questions, not assumptions

    Ambiguity becomes a prompt with the actual choices in it, not a best guess buried in a log.

  • Gates on the dangerous parts

    Destructive and wide-reaching operations stop and wait for an explicit answer, every time.

  • Preview before commit

    Imports run as dry runs, exports preview, and batch edits list what they would touch. You see the shape of the change before it becomes one.

Why you can leave it running

Guardrails

Batch-only writes
Write tools take a set of records and a set of changes. There is no path where the agent nudges one product at a time for an hour with nobody watching the total.
Everything is recorded
Each run keeps its tools, arguments and outcomes. Reviewing what the agent did last Tuesday is reading a page, not grepping a log file.
Provenance is enforced
An import mapping is bound to the file it was built from. The agent cannot apply Tuesday's mapping to Thursday's spreadsheet.
Same rules as a person
Constraints, required fields and permissions apply to the agent exactly as they apply to an editor. It has no privileged back door into the data.
Your provider, your keys
Point it at the model provider you have a contract with. The catalog does not depend on any one vendor being available or affordable next year.
Scoped tools
What the agent may reach for is configuration, not fate. Turn off the tool groups you do not want it to have.

The honest way to evaluate this

Give it a real job on a real catalog and watch the audit trail afterwards. We will set that up with you.