Writing

IdyllicValue: deciding what one AI step passes to the nextDesigning documents that people and AI can edit together.

A document retains its paragraph addresses and separate annotation as it passes between steps.

Motivation

Idyllic was a platform I was designing for people and AI models to work on the same documents. The starting point was the copying and pasting involved in developing a piece of writing through successive chat prompts. Useful material ends up in a document, then comes back into chat when it needs more work.

An Idyllic workflow, which I called an idyll, would capture those prompts as a reusable procedure. The application would carry the material from one task to the next, so a person could concentrate on the writing without managing all the copying and pasting.

One example was a workflow for improving an application to Y Combinator:

  1. Use the source documents to write an initial application draft.
  2. Critique each answer against YC's guidelines, attaching comments and references to the relevant passages.
  3. Revise the draft using those comments, keeping the earlier version available.
  4. Run another critique and revision pass.

Each task needs material produced earlier. The critique needs the draft; the revision needs both the draft and its comments. In a manually run chat, I can paste those into the next prompt or point back to an earlier response. Automating the procedure means the application has to keep track of those relationships and supply the right material itself.

The document is also where the person participates. A critique attached to a paragraph lets me read the feedback beside the writing it concerns. I can edit the draft there and continue working with the model. That makes the relationship between a comment and its passage part of what the application needs to preserve.

IdyllicValue was the proposed common representation for documents used by these workflows. The design question was what a document needed to support so that a person editing it and a model working on it could refer to the same material.

Disambiguating "Context"

It was tempting to describe the workflow by saying that each step receives the previous step's “context.” But in the application example, that leaves several different questions unanswered:

  • Does the critique have access to all the source documents, or only the draft?
  • Does the model receive the whole draft in its prompt, or a reference it can use to read particular sections?
  • Does the next revision receive every previous response, or just the draft and the comments it needs to act on?

These choices affect what the model can do and how much material it has to process. Making a document available does not necessarily mean putting its contents into every model request. A workspace can contain a collection of documents while a particular task reads only one of them.

I was using “context” for both, and also for the history of the workflow itself. That was making the design difficult to reason about. More specific names helped separate the questions:

NameRole in the application workflow
environmentThe documents and functions a step can refer to, such as the draft, the guidelines and a tool for adding comments.
resolvedDocumentThe material assembled after following the references in a prompt, such as the draft and guidelines named by the critique.
llmRequestWhat is actually sent to the model: instructions and selected material, along with settings and available tools.
executionStateThe record of the workflow's progress, including which tasks have run and the results they produced.

The distinction matters when the workflow gets longer. Keeping earlier drafts is useful for inspecting how an application changed. Sending every earlier draft to each new model call is a separate decision, and often unnecessary. The record can retain the full history while a revision request contains only the version being revised and its feedback.

Environment and document belong to individual steps while execution state spans the workflow.
Execution state persists while steps rebuild their environment and resolved document.

This also narrowed the job of IdyllicValue. It concerned the documents being worked on. The request sent to a model and the record of the procedure had their own responsibilities.

Domain-specific documents

The application workflow needs behavior particular to applications: a critique should understand the questions being answered and the guidelines being applied. I had been exploring semantic objects as a way to give different kinds of information their own structure and operations.

That approach asked users to define object types with fields and callable JavaScript methods. It was starting to feel like building a content management system. The setup came before the work: to give a model something useful to operate on, you first had to decide what kind of object it was and describe its schema.

What I wanted from those types was more specific. The model needed enough organization to understand the material, and users needed a way to define what it could do with that material. A document already offered a familiar way to organize information, including references to other documents. Could the behavior live there too?

For the YC application, the draft could remain an ordinary editable document. A custom critique function could use the guidelines and return comments on its answers. Someone preparing the application would work with the writing while that function supplied the domain-specific behavior.

The proposal was to focus on the document as the first shared kind of object, with custom functions written in natural language or JavaScript. Different workflows could add behavior without requiring a separate underlying document type for each one.

Functions for different domains connect to one shared document type.
Custom functions could add domain behavior to a shared document type.

There was still structure to design. A function that comments on an answer must be able to find that answer. Using a familiar document reduced the setup imposed on the user, but it left a precise question for IdyllicValue: how should a program refer to a part of it?

Specification for IdyllicValue

Selecting passages

A request such as “critique this answer” has two parts: the instruction and the passage it applies to. Copying the passage into a prompt lets a model read it, but the application still needs to know which part of the original document the response concerns.

That suggested operations for selecting parts of a document. I was imagining something like jQuery for documents: a way to identify a section and work on it without repeatedly passing around the entire document.

The important choice was what a selection meant:

  • Copied text is sufficient for a summary.
  • A comment that should appear beside an answer needs a link back to that answer.
  • An operation that revises the answer needs to identify what it is allowed to change.

The interface had to distinguish these uses rather than treating every selection as another piece of text.

Annotations

Annotations made that requirement concrete. In the application workflow, the critique should appear as comments on the draft, with the original writing still available underneath. The revision task could then use both the writing and the feedback.

I built an annotation tool in BlockNote, the document editor I was using. It felt like “dancing with AI” because the model could respond inside the material I was working on. I could read a passage and its comment together, without reconciling a separate chat response with the document.

For that interaction, the model needs a reliable way to name the passage it is commenting on. BlockNote gives each block an ID, but its full editor representation was bulky: my pitch deck alone occupied about 45KB. Most of that structure was unnecessary for reading the prose.

One possibility was a compact Markdown view with markers for the blocks. The model would read the text, choose a marker and call the annotation tool with that target and its comment. The application could use the marker to attach the comment to the corresponding block in the editor.

The feedback would remain separate from the passage. Removing a comment should leave the writing intact. A later revision could use the comment as guidance without mistaking it for part of the draft.

A removable feedback note points to a stable paragraph address without changing source text.
Annotations attach feedback to block IDs without replacing the source text.

This is also why a Korean quiz grader seemed like a useful extension. It could attach corrections to my answers using the same annotation operation. The grading instructions would change; the basic interaction of pointing to a passage and commenting on it would already be available.

Interface and representation

These operations gave IdyllicValue a more useful specification than a list of properties such as “structured” or “composable.” Being able to read a document as text is one requirement. Being able to point to an answer and attach feedback is another, with consequences for what the document must retain.

An interface describes those available operations without requiring every caller to know how the document is stored. That separation mattered because the representation was still an open design question. Choosing a complete document format before trying the operations would commit the rest of the application to assumptions that had not been tested.

The starting proposal was deliberately small: wrap an ordinary string in the IdyllicValue interface. That would cover simple text input and output. As operations such as annotation became necessary, the value would need to retain block identities and their associated comments too. The internal representation could develop around those requirements.

The interface remains stable while its internal representation changes from text to addressable blocks.
Operations can require a new representation without changing the interface between steps.

The editor boundary

BlockNote's format was useful for displaying and editing a document, but that did not automatically make it the right format for every workflow. Its block IDs helped locate annotations; its full representation contained more information than a model needed to read.

Keeping IdyllicValue separate from the editor left room for different views of the same material. A model could receive compact text with block markers while the person saw the editable document. An adapter would map between those representations, preserving the correspondence between the text the model read and the block the person saw.

A proposed adapter connects IdyllicValue to the BlockNote editor representation.
An adapter would keep editor structures outside the interchange protocol.

The design depended on that correspondence. A compact representation would be of little use for annotation if the application could no longer locate the passage named by the model.

Instructions inside documents

Custom functions raised another question: when should text in a document be treated as an instruction? An application draft might quote an instruction as source material. A critique function should be able to examine that quote without obeying it.

This becomes a prompt-injection problem when documents can contain executable prompts as well as ordinary writing. The application needs a way to identify the function being invoked and the material that function is supposed to examine. Putting both in the same document does not establish that boundary by itself.

A proposed boundary separates document content from executable instructions.
A generation operation needs a boundary between source material and instructions.

Documents with their own behavior were appealing because they could keep useful procedures beside the material those procedures worked on. How to invoke that behavior deliberately, while keeping the rest of the document as material to read, was still an open part of the design.