Skip to content

Agent Experience

Karthik P edited this page Oct 27, 2026 · 3 revisions

This page sets out what the website provides for coding agents that read the docs on a developer's behalf, how those files are built, and how to check that an agent reading only them writes correct code. Use it when building the agent features or changing a docs page.

Agents are readers here, like people who land from a search engine. The decision to ship llms.txt, llms-full.txt, a Markdown version of every page, "Copy page" and "Open in Claude / ChatGPT" is in the log on Design System. Items marked Proposed are not decided.

What agents need

An agent fetches one or two URLs, reads them as text and writes code from them. It cannot run the demo, switch a tab or hover over a term. So the site gives it:

  1. One file that lists everything (llms.txt), to pick the right page.
  2. One file with everything (llms-full.txt), for agents that load the whole docs at once.
  3. Plain Markdown of every page, at a predictable URL.
  4. Pages that stand alone, written by the rules in Writing Style.
  5. Stable URLs, so links in an agent's context or a project's notes keep working.

llms.txt

llms.txt follows the llmstxt.org proposal: a Markdown file with an H1 (the only required part), a blockquote summary, optional paragraphs, then H2 sections listing links with a short note each. A section named "Optional" holds links an agent can skip. The file may sit at any path and covers the pages under it, so https://workclock.karthik.nz/llms.txt covers the whole site.

It is written by a route handler, website/app/llms.txt/route.ts. Fumadocs' llms().index() writes links as page.url (/docs/install: no host, pointing at HTML; fumadocs-core 16.17.8, source/llms.js), so the route renders its own list with absolute .md URLs.

Proposed: a fixed header before the list, carrying the facts agents most often get wrong:

# workclock

> Fast, correct business-time arithmetic (working hours, SLAs, shifts) for Rust, Python and JavaScript, and a maintained replacement for businesstimedelta, with a compatibility layer for existing code.

Status: pre-1.0; the API may still change in a minor release.

- Install: `cargo add workclock jiff` (Rust; the API uses jiff 0.2 types), `pip install workclock` or `uv add workclock` (Python), `npm i workclock` (JavaScript and TypeScript).
- Requires Rust 1.85+, CPython 3.9+, Node.js 22+.
- Inputs must be timezone-aware: Python `datetime` with `tzinfo`, JavaScript `Date` or ISO 8601 string with an offset, Rust `jiff::Zoned` or `jiff::Timestamp`. Time zones are IANA names such as `Europe/London`.
- JavaScript durations are milliseconds.

## Docs

- [Install](https://workclock.karthik.nz/docs/install.md): packages, requirements and time zone data
- [Quickstart: Rust](https://workclock.karthik.nz/docs/quickstart/rust.md), [Python](https://workclock.karthik.nz/docs/quickstart/python.md), [JavaScript](https://workclock.karthik.nz/docs/quickstart/javascript.md): the same support-desk example
- [Schedules](https://workclock.karthik.nz/docs/concepts/schedules.md): rules, working time and how rules combine
- [Time zones and DST](https://workclock.karthik.nz/docs/concepts/time-zones-and-dst.md): rule zones, gaps and overlaps
- [Errors](https://workclock.karthik.nz/docs/concepts/errors.md): every error class in each language and when it is raised
- [Migrating from businesstimedelta](https://workclock.karthik.nz/docs/guides/migrating-from-businesstimedelta.md): `workclock.compat` and every difference

## Reference

- [Rust API on docs.rs](https://docs.rs/workclock)
- [DESIGN.md](https://raw.githubusercontent.com/Karthik-beta/workclock/main/DESIGN.md): the specification, with an example and an ID for every edge case

## Optional

- [Benchmarks](https://workclock.karthik.nz/docs/benchmarks.md): medians and p95s against businesstimedelta 1.0.1
- [Changelog](https://raw.githubusercontent.com/Karthik-beta/workclock/main/CHANGELOG.md)

DESIGN.md and CHANGELOG.md point at raw.githubusercontent.com so the agent gets Markdown, not GitHub's HTML. The "Status" line is the README's. The Playground and docs.rs are left out of llms-full.txt; docs.rs stays in llms.txt.

llms-full.txt

Every docs page's Markdown in sidebar order, from llms().full() in website/app/llms-full.txt/route.ts (Fumadocs: AI & LLMs). Each page starts with its title as an H1 and its URL on the next line, so an agent can cite it. It is not part of the llmstxt.org proposal but a common companion. Docs pages only: not the landing page or the wiki.

A Markdown version of every page

Page Markdown
https://workclock.karthik.nz/docs/install/ https://workclock.karthik.nz/docs/install.md
https://workclock.karthik.nz/docs/ https://workclock.karthik.nz/docs.md

The rule: the page URL without its trailing slash, plus .md, as llmstxt.org suggests. With trailingSlash: true a page is docs/install/index.html, so docs/install.md sits beside its folder without a clash.

How it is built under a static export:

  • Fumadocs serves /docs/<slug>.md through a Next.js rewrite to its Markdown route. A static export supports no rewrites, redirects or headers (Next.js: unsupported features), and GitHub Pages has none of its own.
  • Instead, a GET route handler with export const dynamic = 'force-static' runs at build time and its response is written as a file (Next.js: route handlers). website/app/llms.mdx/docs/[[...slug]]/route.ts, with generateStaticParams, writes out/llms.mdx/docs/<slug>/content.md for each page, from page.data.getText('processed') (needs includeProcessedMarkdown: true). Fumadocs' static example does the same (examples/next-static/app).
  • Proposed: website/scripts/copy-markdown.mjs, using only Node's fs, runs after next build and copies each content.md to out/docs/<slug>.md. The alternative is to publish the longer /llms.mdx/docs/<slug>/content.md URLs as they are, with no script. The owner chooses; every .md URL on this site assumes the first.
  • The Pages deploy uploads out/ as an artifact (Website Tech Stack), so .md files are served as they are. After the first deploy, check that curl -I https://workclock.karthik.nz/docs/install.md returns 200 with a text content type.
  • Proposed: each page's <head> names its Markdown version: alternates: { types: { 'text/markdown': '<absolute .md URL>' } } in generateMetadata.

What a Markdown version contains:

  • The title as an H1, then the page's first paragraph.
  • Code tabs as one fenced block per language, each under a label naming the language ("Rust", "Python", "JavaScript"). Unlabelled blocks are a bug.
  • Callouts as blockquotes starting with their kind: > **Note:**, > **Warning:**.
  • Absolute links (https://workclock.karthik.nz/...), because the file is read outside the site.
  • The text equivalents of interactive content, which every page already has.

Proposed: a landing page Markdown version at https://workclock.karthik.nz/index.md, from the same data as sections 2 to 6 of Landing Page.

Page actions

Under every docs page's description: Copy page, and an Open menu with View as Markdown, Open in Claude and Open in ChatGPT. Built as a custom component on Fumadocs' MarkdownCopyButton; why it does not use Fumadocs' ViewOptionsPopover, and the visual spec, are on Components. No amber, no service logos.

The prompt passed to Claude and ChatGPT, with the page URL filled in:

Read https://workclock.karthik.nz/docs/install.md, a page of the workclock documentation, so I can ask you questions about it. For anything it does not cover, use https://workclock.karthik.nz/llms.txt.

Link formats, as fumadocs-ui 16.17.8 builds them (layouts/shared/page-actions.js):

  • Claude: https://claude.ai/new?q=<URL-encoded prompt>
  • ChatGPT: https://chatgpt.com/?prompt=<URL-encoded prompt>&hints=search

Neither service documents these, so test both before launch. Links open in a new tab with rel="noopener". The site sends nothing: the visitor's browser opens the other service with the prompt in its address, and the visitor sees it before sending.

Pages agents can use and stable URLs

The writing rules are on Writing Style and the URL rules on Docs Site Structure. Two follow from agents in particular:

  • The title and one-sentence description in front matter are what an agent sees in llms.txt, so each says exactly what the page covers.
  • A published URL never stops working. If a page must move, the old path gets a small HTML page with <meta http-equiv="refresh" content="0; url=..."> and <link rel="canonical"> to the new one (GitHub Pages has no server redirects), and the old .md path keeps a copy of the new Markdown (Proposed mechanism). llms.txt, llms-full.txt and every .md path are as stable as the pages. If versioned docs come later, the unversioned URLs keep pointing at the latest release.

Prompt for your coding agent

At the end of the Install page, a copyable block titled "Prompt for your coding agent". The developer pastes it into their agent before asking it to use workclock. Proposed text:

Add the workclock library to this project to compute working time: working hours, SLAs and shifts, with breaks, holidays and time zones.

Install:
- Rust: cargo add workclock jiff (the API takes and returns jiff 0.2 types)
- Python: pip install workclock, or uv add workclock
- JavaScript or TypeScript: npm i workclock
Requirements: Rust 1.85 or later, CPython 3.9 or later, Node.js 22 or later.

How it works:
- Build one Schedule from rules: WorkDay (working hours on given weekdays), Break (time off on given weekdays) and Holidays (dates removed from 00:00 to the next 00:00).
- Give the schedule an IANA time zone name, such as "Europe/London". Rules without their own zone use the schedule's, holidays included.
- WorkDay defaults to Monday to Friday. An end time at or before the start time is an overnight shift.
- between(a, b) returns the working time between two instants. It is negative when b is before a.
- add(t, d) returns the earliest instant with exactly d of working time after t; sub(t, d) goes backwards. add(t, 0) returns t unchanged.
- next_period and previous_period (nextPeriod and previousPeriod in JavaScript) return a stretch of working time.

Language rules:
- Python: from workclock import Schedule, WorkDay, Break, Holidays. Pass datetimes with tzinfo (zoneinfo.ZoneInfo); naive datetimes raise ValueError. Weekdays are integers, 0 is Monday. Durations are timedelta, or add(t, hours=8). For many inputs, use between_many, add_many and sub_many.
- JavaScript: import { Schedule } from "workclock". new Schedule({ timeZone, rules: [{ type: "workday", start: "09:00", end: "17:00" }, ...] }). Instants are Date objects or ISO 8601 strings with an offset. Durations are numbers of milliseconds. Weekdays are "mon" to "sun". For many inputs, use betweenMany, addMany and subMany. In a browser without a bundler, import init from "workclock/web" and await init() first.
- Rust: use workclock::{Schedule, WorkDay, Break, Holidays, Weekdays}; build with Schedule::builder().time_zone("...").rule(...).build()?. Instants are jiff::Zoned or jiff::Timestamp; durations are jiff::SignedDuration.

Errors: a schedule with no working time fails when it is built (NoWorkingTimeError in Python and JavaScript, workclock::Error::NoWorkingTime in Rust). Every search is bounded, so no call hangs. In Python and JavaScript the errors subclass WorkclockError.

Existing businesstimedelta code: replace its import with "import workclock.compat as businesstimedelta". Most code runs unchanged.

Check your code with this example before using it: a support desk in Europe/London works 09:00 to 17:00, Monday to Friday, with a break from 12:00 to 13:00, and is closed on 2026-12-25 and 2026-12-28. Adding 8 working hours to 2026-12-24 16:30 London time gives 2026-12-30 09:30 London time.

Full documentation for agents: https://workclock.karthik.nz/llms-full.txt

Every statement comes from the README, DESIGN.md or the package's type definitions; when one changes, the prompt changes in the same PR. Before 0.1.0 the block starts with "workclock is not published yet; these install commands work from version 0.1.0."

No MCP server on the site

The site is static files, so it cannot run an MCP server. llms.txt, llms-full.txt and the .md pages give agents the same docs without one. Proposed, for later and only with the owner's approval: a separate package that runs an MCP server on the developer's machine, exposing the docs and between, add and sub as tools; never part of the library packages or the website.

Testing that agents get correct answers

The goal: an agent given only llms.txt and the pages it links writes code that runs and gives the right answer.

Build checks

Proposed, run in the website CI job with Node built-ins only:

  1. out/llms.txt exists, starts with # workclock, has a blockquote first after the H1, and every list item under an H2 is a link.
  2. Every site link in llms.txt resolves to a file in out/. (The lychee link check in CI also covers this.)
  3. Every docs page has its .md file, and llms-full.txt contains every page title.
  4. No .md file contains a relative link (one starting with / or ./).
  5. Every fenced code block in the .md files has a language tag.
  6. The docs' code samples run in CI and match their expected-output comments, as the README's already do (test_readme.py, readme.test.ts).

Agent task check

Proposed: a manual check by the owner before each release and after large docs changes.

  1. Start a fresh coding-agent session with web access to the docs site only: no web search, no repository, no other files.
  2. Give it https://workclock.karthik.nz/llms.txt and one task below, in the language the task names.
  3. Run its code as written in an empty project with workclock installed.
  4. Pass if it runs and prints the expected answer. Note which page the agent read.
  5. Record the date, docs commit, agent, and pass or fail per task in an issue labelled type:docs.

A failure is fixed in the docs, never by making the task easier. If a task fails twice in a row after a fix, open an issue with the agent's code and the page it read.

ID Task Expected answer Source
T1 In Python: a support desk in London works 09:00–17:00 Monday to Friday with a 12:00–13:00 break and is closed on 25 and 28 December 2026. A ticket opens at 16:30 on 24 December 2026 with an 8-hour SLA. When is it due? 2026-12-30 09:30, London time README
T2 In JavaScript, same desk and ticket: how much working time has elapsed by 11:00 London time on 29 December 2026? 9000000 ms (2 hours 30 minutes) README
T3 In Rust: a night shift runs 22:00–06:00 Monday to Friday in UTC. Working time between Monday 2026-01-05 22:00 and Tuesday 06:00, and the instant 4 working hours after Monday 22:00? 8 hours; 2026-01-06T02:00:00Z DESIGN.md O1, O2
T4 In Python: 09:00–17:00 Monday to Friday in UTC, holiday on Wednesday 2026-01-07. Add 2 working hours to Tuesday 2026-01-06 16:00. Thursday 2026-01-08 10:00 UTC DESIGN.md H2
T5 In Python, same schedule without holidays: working time from Monday 2026-01-05 12:00 to Monday 10:00. timedelta(hours=-2) DESIGN.md B4
T6 In Python: 09:00–17:00 Monday to Friday in Asia/Kolkata. Working hours between 10:00 and 12:00 local time on Monday 2026-01-05? 2 hours, from timezone-aware datetimes DESIGN.md N1
T7 In America/New_York, a shift runs 22:00–06:00 every day. Working time between Saturday 2026-03-07 22:00 and Sunday 2026-03-08 06:00 local time? 7 hours (clocks go forward that night) DESIGN.md D1
T8 In Python: build a schedule from a 09:00–17:00 WorkDay with no working days. What happens? workclock.NoWorkingTimeError when built DESIGN.md E2
T9 Here is businesstimedelta 1.0.1 code (a short snippet with WorkDayRule, HolidayRule and Rules). Switch it to workclock with the fewest changes. The import becomes import workclock.compat as businesstimedelta; the rest is unchanged MIGRATING.md

Open items

  • Proposed: the llms.txt header; copy-markdown.mjs and the /docs/<slug>.md URLs (versus the llms.mdx URLs); the text/markdown alternate link; the landing index.md; the redirect-page mechanism; the agent prompt; the build checks; the agent task check; a local MCP server package.
  • After each build, compare llms-full.txt's size with the guides; if the reference pages dominate, move them to "Optional" in llms.txt and out of llms-full.txt (Proposed).
  • Test both "Open in" link formats before launch.

Clone this wiki locally