Concepts
AI integrations
Connectors, operations, adapters, the dispatch lifecycle, replay and skip semantics.
AI integrations
Tidenda lets an organisation wire LLM and JSON-API providers into its editorial workflow. The mechanics live in two document types:
- Connector (
core/ai-connector) — provider credentials and akindthat picks the wire format (OpenAI-compatible, Anthropic, Mistral, or a generic JSON POST). One connector typically powers many operations. - Operation (
core/ai-operation) — a recipe: what slice of the source document to send, the prompt, optional user inputs, the JSON Schema the response must satisfy, and where each piece of the response should land in document space.
A workflow transition with an ai block wires an operation to an
editorial event. When an operator takes the transition, the operation
runs; on success its outputs write back into the document and the
transition lands at its target status.
This page covers the conceptual model and the operation shape. For the workflow side see Workflows; for the JSON shape see Document formats.
Why the connector / operation split
Rotating an API key is a connector edit. Tweaking a prompt is an operation edit. Keeping them separate means the team who manages provider credentials doesn't have to coordinate with the team who tunes prompts, and a single connector outage is visible in one place even when a dozen operations depend on it.
Operations are tenant-scoped; connectors are tenant-scoped. Neither crosses tenant boundaries.
Operation anatomy
An operation declares everything the platform needs to run an AI
call. All fields below live under content unless noted.
Source projection
documentProjection controls what the model sees:
{ "mode": "full" }sends the entire source document minus its history.{ "mode": "fields", "paths": ["title", "content.text"] }sends only the named dot-paths. Use this when the document carries bulky fields the model doesn't need.
A fields projection that selects zero matching paths fails the
operation rather than calling the model with an empty body.
Instructions
instructions is the system prompt. Author it as prose in the
operation editor. It is rendered into the provider-appropriate slot
(system role on OpenAI-compatible providers and Mistral, top-level
system field on Anthropic, instructions field on
generic-json-post).
Static context
staticContext is a list of named JSON blobs the operator authors.
At dispatch, each blob is rendered into the prompt as a JSON object
keyed by name. Use this for things like glossaries, category lists,
style guides — knowledge the prompt should reference but which would
clutter the prose.
Names must be unique within an operation.
Inputs
inputs are values the operator picks at the moment they fire the
transition. Two kinds:
enum— closed list of options, each with a value, label, and optionalextrasJSON the prompt can reference.string— free text, with optional placeholder.
Both support required and default. When an operation declares any
inputs, the transitions menu opens a picker dialog before the
transition fires; the collected values are rendered into the prompt
as a userInputs JSON object.
Output schema
outputSchema is a JSON Schema (Draft
2020-12) the model's response must satisfy. The platform compiles
the schema on every dispatch (so editing it takes effect on the next
call) and validates the response against it after the call returns —
regardless of whether the provider claims to enforce strict mode.
The operation's schema is the final word.
Output targets
outputTargets describes where pieces of the validated response
land. Each target is one of:
path— read a value from the AI output, write it at a document path. Suitable for metadata writes (a category id, a language code, a summary string).{ "kind": "path", "from": "categoryId", "to": "meta.categoryId" }inputPath— write a user-supplied input directly onto the document. The canonical case is translation: the operator picks the target language, the model translates the body,inputPathrecords the language code on the document in the same transition.{ "kind": "inputPath", "from": "lang", "to": "meta.lang" }textPatch— apply a list of{ id, text }patches into the rich-text tree attargetField. Preserves the document's structural shape (images, layouts, captions). Inline marks inside patched elements are not preserved.{ "kind": "textPatch", "from": "patches", "targetField": "content.text" }textbitReplace— rebuild the whole rich-text field attargetFieldfrom a fresh node array. Use for ground-up rewrites where the prior structure isn't preserved.{ "kind": "textbitReplace", "from": "body", "targetField": "content.text" }createDocument— build a brand-new document of the given type from the response. The source document is unaffected. Use for "generate a companion piece from this article" patterns.{ "kind": "createDocument", "from": "translated", "documentType": "core/article" }
Two targets cannot write to the same destination
(to for path / inputPath, targetField for textPatch /
textbitReplace). Multiple createDocument targets are fine.
Sampling and safety
sampling sets provider-specific parameters (model, temperature,
etc.). safety caps the per-tenant per-hour invocation count and the
number of automatic retries on transient failures. Both are tuned per
operation; the platform enforces them.
Export name
meta.exportName is the stable, kebab-case name workflows bind to
via transition.ai.operationRef. Required (and validated) before the
operation can reach usable. Two operations cannot share an export
name in the same tenant.
When a workflow looks up its bound operation, it resolves the latest
usable operation with the matching export name. Editing an
operation does not require re-publishing the workflow — promote
the new version and traffic flips on the next call.
Connector kinds
The connector's kind selects an adapter that knows how to talk to
the provider. All adapters share the same input contract, so swapping
a connector on an operation doesn't require touching the operation.
| Kind | Targets | Structured output via |
|---|---|---|
openai-compatible |
OpenAI, Azure OpenAI, Ollama, Groq, Together, vLLM | response_format: { type: "json_schema", strict: true } |
anthropic |
Anthropic Messages API | A forced tool call whose input_schema is the operation's schema |
mistral |
Mistral La Plateforme | A forced function call whose parameters is the operation's schema |
generic-json-post |
Anything else (internal AI services, non-LLM APIs, vendor-locked APIs) | A flat envelope POSTed to a single URL; response satisfies the schema directly |
local-service |
Built-in local AI service shipped with the platform (translation, summarisation, IPTC categorisation) | Same envelope as generic-json-post; identified separately so cost ceilings can treat the calls as zero-spend |
For openai-compatible, anthropic, and mistral, the platform's
own post-call validation runs regardless of provider-side enforcement.
Built-in local AI service
The platform ships with three local enrichment endpoints (the
local-service row above). Models are loaded into a container that
runs alongside the rest of the platform; tenants pay no per-call
cost and no document text leaves the deployment.
| Capability | Model | Where it runs |
|---|---|---|
| Translation between operator-picked language pairs | facebook/nllb-200-distilled-600M |
Bundled local service |
| Short summarisation for article descriptions | csebuetnlp/mT5_multilingual_XLSum |
Same |
| Zero-shot IPTC top-level categorisation | MoritzLaurer/mDeBERTa-v3-base-mnli-xnli |
Same |
All three are open-weight Hugging Face models and the model
identifiers above are the defaults. Each capability has its own
operation slot in the operation catalogue
(translate-from-wires, summarize-from-wires,
categorize-newswire) — an organisation that wants to swap the
local model for its own LLM duplicates the slot's operation,
picks its own connector, and the platform routes through the
override automatically.
Newswire categorisation on arrival
Every incoming core/newswire document is classified against the
17 top-level IPTC MediaTopic categories the moment ingestion
finishes. The classification is asynchronous — the wire is
visible to the editorial UI immediately at usable, and the
category lands on meta.ipctCategory once the classifier
returns. A classification failure leaves the wire usable but
uncategorised; the operator can still open it, create an article
from it, and so on.
The category drives two surfaces in the newswire view:
- A pill on the wire row showing the topic in short form (Sport, Politics, Business…). Clicking the pill toggles that category in the column filter.
- A multi-select category filter in the toolbar. Categories combine OR-wise within the filter and AND-wise with the source filter.
Categorisation never propagates into articles created from a wire. Articles carry their own categorisation if and when the editorial flow adds one.
For generic-json-post, the request body is:
{
"instructions": "...",
"document": { "...": "..." },
"staticContext": [{ "name": "...", "data": "..." }],
"outputSchema": { "name": "...", "schema": { "...": "..." } },
"sampling": { "model": "...", "...": "..." }
}
The response must satisfy the operation's output schema directly (no envelope to strip).
Binding to a workflow
A workflow transition that fires AI carries an ai block:
{
"from": "draft",
"to": "translated",
"ai": {
"operationRef": "translate-article",
"onFailure": "translation-failed"
}
}
operationRef— the operation'smeta.exportName. Resolved at dispatch to the latestusableoperation with that name.onSuccessis implicit: the transition'sto.onFailureis optional. When set, content-shaped failures (validation, projection, response parse) transition the document there instead of parking it. Infrastructure failures (auth, cost limit, connector disabled) never transition — the document stays put with the error visible so a config fix can let the operator retry.
A worked example
An operation translate-article declares one enum input named
lang with options for Swedish, Danish, Norwegian. Its output schema
describes { title: string, patches: [{ id, text }] }. Its targets
are:
[
{ "kind": "path", "from": "title", "to": "title" },
{ "kind": "textPatch", "from": "patches", "targetField": "content.text" },
{ "kind": "inputPath", "from": "lang", "to": "meta.lang" }
]
A workflow transition draft → translated references the operation.
When an editor takes the transition the picker opens, the user
selects Swedish, the operation runs, and the document arrives at
translated with a translated title, patched body, and meta.lang = "sv" — all in a single version bump.
Dispatch lifecycle
- The operator takes an AI-bound transition. If inputs are declared, the picker dialog opens first.
- The platform stamps
meta.aiPendingon the document, locks it editing-wise, and displays an "AI working" banner. - A pre-AI snapshot of the document is captured for rollback.
- The dispatcher projects the document per
documentProjection, parses static context entries, synthesises theuserInputsblock from the picker values, and calls the connector. - The response is validated against
outputSchema. - On success, every target in
outputTargetsis applied in a single transaction; the document arrives at the transition's target status andmeta.aiPendingis cleared. createDocumenttargets run afterwards as new documents in their own right.- The invocation log records the call (prompt, response, duration, per-target warnings) regardless of outcome.
On permanent failure the document either transitions to onFailure
(if set and the failure is content-shaped) or stays at its current
status with the error visible. The operator can cancel a pending
operation from the banner; Cancel clears aiPending without applying
any writes.
Failure categories
| Category | Class | Retried automatically? | Notes |
|---|---|---|---|
| Network blip / 5xx | transient | yes | Up to safety.maxAutoRetries. |
| Provider timeout | transient | yes | Same. |
| Validation against schema | permanent | no | Transitions to onFailure when set. |
| Projection error | permanent | no | Same. |
| Response parse error | permanent | no | Same. |
| Auth, cost limit | permanent | no | Document stays put with the error visible. |
| Connector disabled | permanent | no | Same. |
A document that stays pending past the stale timeout (default 10 minutes) is swept clean automatically; the invocation log records the timeout and the document returns to its previous status.
Snapshots and rollback
Every AI apply captures a pre-AI snapshot first, identified by the operation id and a timestamp. The snapshot is the rollback point. In the admin interface, the invocation log shows the snapshot reference for each completed operation; rolling back is a manual step (push the snapshot bytes back as the current document state) — there is no one-click revert yet.
For the integrator's view of an AI step from outside the platform,
see Receiving webhooks. The
webhook fires only on the final transition; the AI step is invisible
to a webhook receiver beyond a by.kind = "system" credit on the
history entry.
Related pages
- Workflows — how transitions and AI bindings fit together.
- Document formats — the JSON shape of an AI operation document.
- Receiving webhooks — the downstream view of an AI-driven transition.