Architecture
How the kenneth assistant is built: the request path, grounding in public content, and where each trust boundary sits.
Text checked against the code and documentation on .
kenneth is a portfolio first. Every page works without the assistant. The assistant is an add-on that finds facts already published on this site. When it is off, rate limited, over budget, or the provider fails, the panel shows plain links to Projects, Experience, Resume, and Contact.
The request path
- BrowserSends the conversation
- /api/chatRate limit, validation, budget
- ModelWorkers AI, bounded steps
- ToolsRead the public registry
- Cited answerServer-checked sources
- The browser sends the conversation to POST /api/chat, using the app's HTTP client.
- The server checks the feature switch, a per-visitor rate limit, and that one request per visitor runs at a time. It then validates the body: size, roles, text length, and that the page the visitor says they are on is a real page.
- The server counts the request against an app-wide daily budget. Invalid requests are rejected before this step and spend nothing.
- The model runs on Cloudflare Workers AI. The default model is @cf/zai-org/glm-4.7-flash; each evaluation run records the model it used. A request has at most 3 model steps, 1,024 output tokens, and a 25 second timeout.
- On the first step the model must call a retrieval tool. A second tool can ask for a result card.
- The server resolves citations and cards from its own registry, then streams typed parts: text, sources, notices, and activity.
- The browser renders those parts as plain text and a fixed set of cards. It never renders model-written markup or links.
Retrieval and grounding
The portfolio corpus is a small set of typed, validated records: projects, experience, education, skills, and one evidence source. Search is deterministic: technology and title matches outrank prose. There is no vector database. A vector store would be justified only if measured retrieval failures showed keyword search missing relevant content; no such measurement exists.
- Nine read-only tools read the public registry: getSkills, getProfile, searchProjects, getProject, getExperience, getEducation, getSkillEvidence, getContactOptions, and suggestNavigation. None can fetch a URL, read a file, run a command, or change data.
- Every record carries an ID and the IDs of its sources. The model cites a record with its ID in square brackets.
- The server keeps a citation only if a tool returned that ID during the same request. It then attaches the title and link itself. A link the model writes never becomes a link.
- If the tools find nothing, the server writes a fixed reply instead of letting the model answer from general knowledge. Text written before any tool returned real portfolio content is dropped.
Trust boundaries
| Input | Treated as | Enforced by |
|---|---|---|
| Visitor text and earlier assistant turns | Untrusted data, never instructions | Only text parts are read. Tool results, system text, and model settings in a request are ignored. |
| Model output | A proposal | Tool names are an allowlist. Inputs are parsed by Zod before a tool runs. Citations are checked against tool results. |
| Tool results | Public data only | Tools read a registry filtered twice to public records and approved sources. Results are clipped and capped at 8,000 characters. |
| Result cards | Application-owned | The model names a card type and record IDs. The server fills in every title, label, and link, and the browser re-validates the card. |
| Links and navigation | Allowlisted | Only published pages, case studies, approved sources, and public contact links can become links. |
| Activity and Agent Studio events | Server facts | Each event is built from a fixed step name, a measured time, a status, and a count. No model or visitor text is passed through. |
What it does not do
- No web retrieval, no write tools, and no access to repositories, email, or databases.
- No stored chat history. A conversation lives in the visitor's browser tab.
- No distributed rate limit yet. The guard is in memory, so it limits one server instance, not all of them. That is a launch task, not a finished control.
- No architecture diagram block. No approved diagram exists, so the card type is not built.