Intake Forms
Productivity
When an agent needs facts from a person — which environment, which account, what actually happened — the usual approach is to have the model ask in prose and then parse the reply. That puts the model between the person and the record, and whatever it misreads becomes what you filed. This template opens a real form instead, and files exactly the fields that come back.
What you get
Three tools over one intake API.
- **
intake.submit** — opens a form with a category, a summary, optional detail and an urgency flag, then files the answers. It takes no arguments at all: there is nothing for the model to fill in on the person's behalf. - **
intake.amend** — re-opens a shorter form against an existing submission and appends what was missing. It appends rather than replaces, so the first account of an incident survives the second. - **
intake.get** — reads one submission back. No form opens, so an agent can answer "what did I file?" without interrupting anyone again.
The reason to do it this way is the audit trail. A filed submission is what a person typed into named fields, not a model's summary of a conversation. When somebody asks six weeks later what was reported, the answer does not depend on how well a model paraphrased that day.
What you need
- An intake system, for the Intake API host and Intake API token fields. Any service that accepts
POST /v1/submissionswill do — a ticket queue, a spreadsheet behind a small API, a table you own. - An OpenID Connect provider, for the JWKS endpoint and Token audience fields.
Two optional fields control patience: How long a form stays open and Overall call ceiling. Leave them alone unless your people are slower or busier than the defaults assume.
How it works
Both writing tools are pipelines whose first step is an elicitation in form mode. The gateway sends the client a JSON Schema, the client renders it, and the call suspends until a person answers or the timeout expires.
The step after the form is the one that matters:
- kind: cel_gate
expression: 'steps.ask.output.action == "accept"'A client may return decline or cancel, and **both of those carry a content object too**. Filing on anything other than accept would record an answer the person had explicitly chosen not to give. The gate is what makes dismissing the form mean nothing was filed.
intake.submit declares an empty input_schema on purpose. A tool that accepted a summary argument would let a model file on someone's behalf and still look like it had asked them.
Two timeouts, not one: the form's own ceiling and the pipeline's. The pipeline value must stay above the form value, because a pipeline that gave up first would abort the call while the person was still typing — and they would submit into nothing.
The first five minutes after it boots
- Call
intake.submit. Your client should render a form, not print a question. If it prints a question, the client has not implemented elicitation and this template cannot work with it. - Fill it in and submit. Check the response carries the new submission id.
- Call
intake.submitagain and dismiss the form. Nothing should reach your intake API — confirm that in its own logs, not in the tool result. - Call
intake.amendwith the id from step 2 and add a line. Confirm the original text is still there. - Call
intake.getand read both back.
Notes
- Elicitation is a client capability. A client that does not implement it cannot render the form, and the call fails rather than degrading to a prompt. That is the correct behaviour for this template: a silent fallback to "the model asks in chat" would reintroduce exactly the problem it exists to solve.
- **
urgentis a boolean the person sets**, not a priority the model infers. Whether your intake system pages anyone on it is your decision, made on the far side of the API. - The audit sink is on and fail-closed. A tool that files reports on behalf of people is one whose own record you will eventually be asked for.
- Nothing here is Notion-, Jira- or Zendesk-shaped. The three tools speak a small REST shape you can put in front of whatever you already run, which is why the upstream field asks for a host rather than a vendor.