Skip to content

The AI assistant

The third pane of the Intent Editor (toggled by the toolbar's discussion icon) is a natural-language assistant that edits app.intent at your altitude. It proposes a patch to the intent, never a re-emitted model file - the "edit shape, not file shape" contract from the manifesto.

It is optional. Turn it off and the editor is still a structured YAML editor a person drives directly. AI is an accelerator, never a single point of failure.

How it works

  1. You describe a change in plain language ("add a country field to Customer").
  2. The browser sends { yaml, message, history } to POST /services/ide/intent/agent.
  3. The server calls the Anthropic Messages API and returns { reply, proposedYaml }. Claude answers either with a plain-text clarifying question, or by calling a single propose_intent tool that returns the complete updated YAML.
  4. The editor renders the proposal as a Monaco diff against the current buffer.
  5. Accept replaces the buffer (still unsaved - dirty tracking and the debounced re-parse fire as usual). Reject discards it.

You still Save and Generate yourself. The assistant never writes to disk and never runs the generators.

Returning the whole file and diffing it locally was chosen over LLM-authored unified diffs (fragile to apply) and structured edit-ops (which lose comments and formatting). The system prompt teaches Claude the diff-stability rules: change minimally, preserve key order and comments, append rather than reorder.

The system prompt

The assistant's system prompt is an externalised, reviewable resource (intent-assistant-guide.md, loaded from the classpath), not an inline string - so it can be maintained as documentation and kept in lockstep with what the parser enforces. It documents the full schema, including the declarative-glue catalog and the trigger businessKey / businessKeyStrategy, plus the recipient grammar (literal / direct field / one-hop relation.field, no braces).

The boundary, and what happens at it

There is a line in the DSL, and it is a design decision rather than a gap: the intent models what the application means; protocol, algorithm and statutory form are how, and live outside it. When a requirement crosses that line the assistant must report it, never quietly substitute the nearest thing the DSL can express - silently turning "the system identifies the driver" into "an officer identifies the driver" changes the contract where the developer cannot see it.

Three categories, and the extension point that carries each:

CategoryExamplesWhere it goes
Protocol adaptationcertificates, acknowledgements, retries, batch or file transports (an SFTP drop, a signed archive, polling)a Camel route in the same project, feeding the entity's ordinary write path. integrations:, inbound: and outbound: are deliberately one-line call-outs
Algorithmchecksums (national identifiers, account numbers), fuzzy matching, scoring, policy-driven tie-breakingcalculatedActionOnCreate / calculatedActionOnUpdate on a field or to-one relation, or a serviceTask delegate: - both implemented under custom/
Statutory or designed formthe exact legally mandated print layoutthe .print template, generated create-if-absent by design and adapted by hand

When it hits one, the answer has three parts: name the category and why; propose the intent-side wiring for the extension point (the serviceTask with its delegate:, the field with its calculatedActionOnCreate:) rather than a semantic downgrade; and report it in the tool call's boundaries array - { requirement, why, extensionKind, suggestedClass }. The editor and the Builder render each entry as its own bubble, not buried in prose, so it survives being read quickly and can be forwarded verbatim.

Two things follow the report:

  • The stub is generated. A named calculatedAction* the project would own gets a generate-once custom/<Class>.java scaffold, so the boundary is a file to open rather than a repository referencing a class nobody wrote. It is scaffolded only when the class is unqualified or under custom., and only when the entity's imports: do not already bring that name in from elsewhere.
  • The other assistant takes over. The intent assistant never emits Java, TypeScript or SQL; the Workbench Assistant view helps write the custom/ class, in its own conversation. The boundary is explicit in the product, not only in the prose.

Transcript discipline

The browser keeps two lists: a display log (which may hold errors and UI notes) and a clean alternating user/assistant transcript sent as history. The current YAML is embedded fresh in every latest user turn, so the model always diffs against ground truth even after an Accept. Tool calls are not replayed. A failed turn pops its dangling user turn so the next request stays alternating.

Configuration

The API key is read server-side via DirigibleConfig and is never sent to the browser. With no key configured the assistant is disabled and the endpoint returns 412.

Environment variableDefaultPurpose
DIRIGIBLE_INTENT_AI_API_KEY(blank)Anthropic API key. Blank disables the assistant.
DIRIGIBLE_INTENT_AI_MODELclaude-opus-4-8model id
DIRIGIBLE_INTENT_AI_BASE_URLhttps://api.anthropic.comAPI base URL
DIRIGIBLE_INTENT_AI_MAX_TOKENS8192max response tokens
DIRIGIBLE_INTENT_AI_VERSION2023-06-01Anthropic API version header

The whole feature is one server-side bridge (IntentAgentService + IntentAgentEndpoint); the JDK HttpClient makes the call. The key never leaves the server.

See also

Released under the EPL-2.0 License.