How the Drupal Association raises money can shape who influences the project and which work gets sustained. James Abrahams makes that tension central to his board candidacy.
A backend engineer guide to Single Directory Components in Drupalbhavinhjoshi
Bridging the gap between custom modules and strictly typed components
Single Directory Components completely changed how we handle the frontend. But if you spend most of your time engineering the backend, SDC can feel incredibly frustrating.
You build a massive render array, pass it to the template, and the strict schema rejects it instantly. The days of dumping raw entity objects into Twig are over.
When you work with SDC, the component dictates exactly what data it accepts. This guide details the cleanest way to format your data and bridge the gap between custom backend logic and strict frontend schemas.
Implementing the Component Workflow
If your backend architecture does not format data perfectly, the system throws a fatal error. Follow these steps to ensure your data matches the strict schema requirements.
1. Define your component schema
Every component needs a YAML file. This is the contract your backend must follow. Keep your properties simple and predictable.
name: Article Card
description: A strict component for displaying article summaries.
props:
type: object
required:
- title
- url
properties:
title:
type: string
title: Article Title
url:
type: string
title: Target Link
summary:
type: string
title: Teaser Text
2. Format the render array
Backend engineers usually try to pass the entire node object. Do not do that. Extract the exact strings your schema demands. Set the render array type to component so Drupal knows how to route it.
Notice how we completely avoid preprocess functions here. The template simply accepts the data contract and renders the output.
Key Considerations
Never pass full entity objects into SDC props. Always extract the raw values first.
Validate your schema early. A mismatched type will trigger a rendering error that brings down the page.
Keep your #props mapping clean by handling all complex business logic in the controller rather than the template.
This strict separation keeps your backend logic secure and your templates highly predictable. Adapting to Single Directory Components requires a mindset shift for backend developers. But it is the absolute best way to scale enterprise software without creating a massive technical debt trap for future engineers.
Open-source code may remain available while the services, organisations, licences, and support arrangements around it change. Drupal delivery teams need accurate dependency records and workable continuity plans throughout the software's operational life.
The European Commission wants public bodies to become stronger users and contributors to open source, supported by fairer procurement, maintenance funding, security measures and long-term stewardship.
This post is adapted from the DA Insider, the Drupal Association's monthly newsletter. Subscribe here to get it in your inbox each month.
A note from our interim CEO
Dear Drupal community,
Open source hums along on the work that just gets done. As I step into the interim CEO seat, I'm making a point to notice the sheer volume of work powering this ecosystem, from the DA and beyond. Here's some of what has come together in the past month:
Our engineering team migrated hundreds of projects to GitLab, security issues included, and kicked off an RFP to make launching a Drupal site dead simple.
DrupalCon Rotterdam is ready and in the homestretch.
The DrupalCon Orlando call for speakers opened August 4. If the event converts even half the enthusiasm of the local team (or the cuteness of Bytes the Gator), it's going to be one to remember.
We're building a new front door to introduce Drupal to new audiences and evaluators, and we're looking for help.
The community has 14 global events dropping this August and partnership groundwork under way in Burkina Faso.
Nominations are open for the Women in Drupal Award, an honor I was humbled to receive back in 2023.
My goal as interim CEO is straightforward: make sure the Association's foundation is resilient enough to support all this energy. The first step is helping all of us notice and appreciate the work that already "just happens."
I hope you enjoy this month's newsletter and everything everyone's been building. And one final note: board elections are open. Please vote.
Tiffany Farriss Interim CEO
Vote in the 2026 At-Large Board Election - closes 14 August
If you're a Ripple Maker, your ballot arrived by email from Helios Voting on 22 July. Voting closes 14 August 2026 at 23:59 UTC, so there's still time to get to know the candidates: read their profiles and leave questions on the election details page, catch the Open Community Forum recording on our YouTube channel, or revisit the async conversation in #drupal-association on Drupal Slack. Every vote counts - make yours matter.
DrupalCon Rotterdam is in the homestretch
DrupalCon Rotterdam 2026 is ready. Join the global Drupal community for four days of learning, collaboration, and connection - explore the program, meet the speakers, and start planning your experience. Secure your ticket now.
A more focused program with fewer concurrent sessions and an emphasis on high-quality, impactful content. Updated session tracks reflecting the evolving Drupal ecosystem. And a new pathway for first-time speakers: if you've never spoken at a DrupalCon, DrupalCamp, or other Drupal event, you can submit to the new Poster Session - selected presenters showcase their work at the Monday Welcome Reception and present a 10-minute session on the Lightning Stage.
And keep an eye out for Bytes the Gator, the DrupalCon Orlando mascot, who'll be visiting Drupal events around the world between now and March 2027 - with a chance to win a free registration to DrupalCon Orlando 2027 along the way.
Celebrate the women shaping Drupal's future
Nominations are open for the Women in Drupal Award, sponsored by Jakala, recognising women whose work strengthens the Drupal community - in the projects they build, the teams they support, the ideas they bring forward, and the space they create for others to grow. Know someone whose contribution deserves recognition? Submit a nomination.
Drupal Steward: extra time when it matters most
When highly critical vulnerabilities emerge - like SA-CORE-2026-004, a SQL injection in Drupal core that anonymous users can trigger - every minute matters. Drupal Steward is a security service from the Drupal Association that gives you extra time to respond before vulnerabilities can be widely exploited: early notification of highly critical issues, recommended WAF mitigation rules, and access to security expertise, in coordinated collaboration with the Drupal Security Team. It's available in a Community Tier for smaller site portfolios, plus Small, Mid-Size & Enterprise tiers for organisations that want full control. Referral incentives are available for Drupal Certified Partners.
Behind the scenes with the engineering team
The migration of projects to GitLab issues continues - including security issues and hundreds of Ripple Maker projects - with GitLab soon to be enabled by default for all new projects, alongside updated contribution docs and a new custom commands reference. The team has also kicked off a collaboration with Alpha-Omega through their Security Engineer in Residence program to triage and respond to the growing wave of AI-generated security reports. And an RFP is under way for the Drupal Site Template Marketplace, focused on closing the last mile from template selection to live hosted site.
Help build Drupal's new front door
We're building a dedicated product marketing site for Drupal - a purpose-built, marketing-led site designed to reach the people who haven't heard of Drupal yet: marketers, IT directors, and enterprise decision-makers evaluating CMS platforms.
High-priority tasks are being added to the promote_drupal project on GitLab - real, scoped pieces of design, content, video, and strategy work with significant contribution credits attached, with more added on a rolling basis. If something catches your eye, reach out to Ryan Witcombe at ryan.witcombe@association.drupal.org or @RyanWitcombe on Drupal Slack.
A milestone for open source in West Africa
On 15 July, the Drupal Burkina Faso Association, led by its president Seferiba Salif Soulama, met with Burkina Faso's Minister of Digital Transition, Dr. Aminata Zerbo/Sabane, to explore how Drupal can support the country's digital future. The meeting marks a significant step toward a formal partnership between the Ministry and the Drupal Burkina Faso Association, with Drupal at the heart of Burkina Faso's digital modernisation agenda.
This is what open source looks like in action: communities, governments, and technology coming together to build something that belongs to everyone. Read the full story.
One more thing: The AI Byte
The Drupal AI Initiative team has launched The AI Byte, a monthly LinkedIn newsletter curating the best content across the web about Drupal AI - new capabilities, case studies, events, and webinars. Subscribe on LinkedIn.
Ajit Shinde, Sr. Drupal Developer and a longtime open source contributor, built the workspace tools in the Tool Belt module so AI agents in Drupal work under the same staging and review rules as human editors.
AI assistants can already generate content, update fields, and perform administrative tasks in Drupal. The more interesting question is not whether an AI can perform these operations, but how we allow it to do so safely and predictably.
Earlier this year, Tag1 joined the Drupal AI Initiative as a Gold Certified Partner, and our first contribution is extending Workspaces so AI agents work under the same staging, review, and rollback framework as human editors. The workspace tooling in this post is part of that governance work.
Tool API: The Foundation
To get Workspaces talking to AI agents, we built a new module called Tool Belt on top of Drupal's Tool API. The Tool API does the underlying work, so it's worth understanding first.
The Tool API provides the system that allows Drupal to expose structured, callable operations to AI agents. It defines how tools are described, how inputs are validated, and how results are returned.
In short, it turns Drupal functionality into well-defined actions that an AI can safely invoke.
Tool Belt builds on that.
What Tool Belt Adds
Tool Belt provides ready-made tools that AI agents can use to perform common Drupal operations. Instead of asking a language model to understand Drupal internals, we give it a controlled set of capabilities.
I worked mainly on workspace-related tools, which ship as the Tool Belt - Workspace submodule (tool_belt_workspace). These allow an AI workflow to:
Create and reuse workspaces
Switch between them
Generate preview links
Inspect changes
Safely move content toward publishing To see why this matters, let's look at a common workflow: asking an AI assistant to create an article.
From a Prompt to a Drupal Operation
Imagine an editor asks:
Editor: Create an article announcing our upcoming Drupal event.
This requires more than generating text. The AI must decide:
Which content type to use
Which fields to populate
Whether to stage changes in a workspace
How the editor will preview the result
What else might be published alongside it Without structure, this can lead to incomplete content or unintended publishing.
Tools turn each of those decisions into explicit, controlled operations the agent must call, rather than leaving it to guess.
Workspace Tools in Practice
Drupal Workspaces allow changes to be grouped and reviewed before publishing. This is especially useful for AI-generated content.
Reusing or Creating Workspaces
Instead of always creating a new workspace, the agent can:
Check the active workspace
Reuse an existing one if appropriate
Create a new one only when needed
Switch to it before making changes This keeps related changes together and avoids clutter.
Previewing Content
After creating the article, the assistant can generate a temporary preview link. This allows editors (and stakeholders) to review staged content without exposing it publicly.
Reviewing Changes Before Publishing
A workspace may contain multiple changes. Before publishing, the assistant shows a summary:
Assistant: This workspace contains the new article and related updates. Do you want to publish these changes?
This ensures publishing is always intentional.
Reverting or Deleting
If the result is not useful, the workspace can be reverted or deleted, with explicit confirmation to avoid mistakes.
The AI Article Workflow (Simplified)
A typical workflow looks like this:
Editor request. The editor asks for an article.
Assistant confirmation. The assistant confirms the structure and whether to use a workspace.
Workspace preparation. The workspace agent selects or creates a workspace and switches to it.
Content creation. The content agent generates and saves the article.
Preview and review. The assistant returns a preview link.
Change inspection. The assistant lists all workspace changes.
Publish decision. The editor confirms publication. This keeps humans in control while AI handles repetitive tasks.
Assistants, Agents, and Tools
It helps to separate responsibilities:
Assistant (orchestrator): manages the conversation and workflow
Workspace agent: handles staging and publishing
Content agent: creates and edits content
Tools: perform the actual Drupal operations This modular approach makes AI behavior easier to configure and reason about.
Prompt Configuration
Below are the actual prompts used in this setup.
Orchestrator Agent Prompt
This agent uses the other two agents (content agent and workspace agent) as tools.
## Role
You are the Orchestrator for a Drupal 11 AI site. You own every word the user sees. You never do content or workspace work yourself - you route it to two specialists and turn their machine-readable results into user-facing replies:
* Workspace Agent - workspace lifecycle: create, reuse, switch, publish, revert, delete, preview links, tracked changes.
* Content Agent - creating and editing content entities (any bundle, primarily nodes).
Specialists never talk to the user. They return a success result, a `blocked: need <X>` line, or a failure. You translate those into an answer or exactly one question.
## Scope
In scope: content creation/editing, workspace staging, preview links, publishing, and the lifecycle actions above. Out of scope: users and roles, site building and fields, moderation workflows, emails, configuration, anything else. For an out-of-scope request, reply in one sentence stating what you can do and that this request is outside it - do not call a specialist.
## The one approval question
A "task" is one user goal you carry from its first message to completion. Before the first content write of a task (create, edit, or save a content entity), ask exactly ONE approval question combining:
* the rough structure you plan to write: bundle, title, and a one-line outline per field (never full body text), and
* the staging choice: stage in a workspace, or go live now?
If the user already stated the staging choice (they named a workspace, asked for a new one, or said live), ask only for structure approval.
When this question applies:
* Ask it BEFORE calling any agent for a write. Only read-only checks may precede it.
* Never ask it for read-only requests, or for lifecycle actions on an existing workspace (publish, revert, delete, switch, preview) - those have their own rules below.
* Ask it once per task. After the user approves, run the routing sequence to completion with no further approval question, and do not re-ask unless the user changes direction.
This limit governs the APPROVAL question only. A specialist `blocked:` clarification (see Blocked results) and a destructive-action confirmation (see below) are separate and are not counted against it - you may still ask those.
## Task mode
From the staging choice, fix one task mode for the request and keep it:
* live - the user chose live.
* stage - the user chose staging or named/requested a workspace.
Reuse-vs-create is the Workspace Agent's decision, not yours: it reuses the active non-live workspace, or creates one when Live is active. Only if the user explicitly asked for a new/fresh/separate workspace, say so in your setup instruction and the Workspace Agent will create one regardless of the active workspace. Never present a reused workspace as newly created.
## Routing
Run each step at most once per task and track what is done.
Per task, the Workspace Agent gets AT MOST TWO calls, in this order: one workspace SETUP call, then one PREVIEW-LINK call after the content exists. Never create a second workspace for the task, never repeat setup, and never request the preview more than once. A read-only check does not count. A later explicit lifecycle request (publish, revert, delete, switch) is a NEW task, routed normally.
The preview link must be generated AFTER the content is created so it can redirect straight to the new page - the setup call establishes the workspace, the content call yields the node ID, and only then do you ask for the preview. Never request the preview before you hold the node ID.
Whenever you call the Content Agent, include the user-approved structure and state that the user already approved it, so it does not re-present it.
### stage1. Call the Workspace Agent to establish the workspace for this task ONLY - do not ask for a preview yet. Suggest a short label for a new workspace; state the label when the user explicitly asked for a new one.
2. Call the Content Agent for the content work. Capture the node ID it returns.
3. You MUST now call the Workspace Agent a second time for the preview link, passing the redirect target `/node/<ID>` using the node ID from step 2. This call is mandatory - the only way to obtain a preview URL is from this tool result. Do not skip it, do not answer the user before it returns, and never invent, assume, or describe a preview link you have not received from this step.
4. The Workspace Agent's preview result contains the preview URL as plain text. Copy that URL VERBATIM - character for character - as the FIRST line of your reply, as plain text with no markup around it (see the Preview link format rules in Output). If step 3 did not run or returned no URL, you have NO preview link: omit it entirely and add one short line saying the preview could not be generated.
### live* Call the Content Agent directly; do not create or reuse a workspace. Read-only workspace checks are still allowed.
### Later lifecycle actions
Route publish, revert, delete, and switch to the Workspace Agent for that specific action, subject to the confirmations below.
## Destructive confirmations
Confirm before executing, even when the request was explicit - one confirmation each. If the user already confirmed in the same message, proceed.
* Publish: first get the tracked-changes summary from the Workspace Agent, confirm while showing what will go live, then publish.
* Revert: confirm naming the workspace label and ID, then revert.
* Delete: confirm naming the workspace label and ID, then delete.
## Task continuity
A reply that answers your most recent question continues the current task with the same task mode. Do not restart workspace setup unless the user explicitly changes direction.
## Blocked results
When a specialist returns `blocked: need <X>`, ask the user exactly one natural-language question carrying any options the specialist provided (e.g. a list of matching nodes). When answered, resume the same task with the same task mode. This is a clarification, not a new approval question.
## Failure handling* Never retry blindly. Retry a specialist step at most once, and only with corrected input.
* Exception: never retry the Workspace Agent setup step. If setup fails, do not call the Workspace Agent again for this task - a retry would create a second workspace. Stop and surface the failure per the rule below.
* If a step fails twice, stop: state in one sentence what succeeded and what failed, include the actionable error output, and offer up to three next steps.
## Output
Keep replies short and operational.
### Preview link format
The Workspace Agent returns the preview URL as plain text (e.g. `https://cms.ddev.site/workspace-preview/W7MIUT5l2ByB`). Your job is only to RELAY it - copy that URL to the FIRST line of your reply exactly as received, as plain text. Do NOT wrap it in a markdown link or an HTML anchor, and do NOT alter, shorten, or retype it - just the raw URL on its own line.
* Correct: `https://cms.ddev.site/workspace-preview/W7MIUT5l2ByB`* Wrong - and forbidden: any `<a ...>` tag · a markdown link `[Preview the page](...)` · descriptive text with no URL · substituting a URL of your own.
If the Workspace Agent returned no URL, write NO preview link and NO preview link text at all; instead add one short line stating the preview could not be generated.
### Staged-content reply
After staged creation/edit, the reply is: the preview URL relayed verbatim as plain text (per the format above) on the first line, then one short line naming the page (title and node number). Do NOT include the workspace label or workspace ID, and do NOT offer to publish, ask about going live, or suggest any next step - end after stating what was created. A standalone preview reply carries the URL only - no workspace label, workspace ID, or node links.
Content Creation Agent Prompt
This agent uses tools like tool:tool_belt:entity_field_value_definitions and tool:tool_belt:entity_field_values.
## Role
You are the Content Agent for a Drupal 11 AI Site. You report to the Orchestrator, never directly to the user. You create and edit content entities (any bundle), primarily nodes.
## Tool execution rule
Use the entity tools to do the work; do not describe what you would do. Do not call tools speculatively. If you are missing information a tool requires, return a `blocked: need <X>` line rather than guessing.
## Site schema (authoritative - do not guess field names)* Bundle `article`: `field_body` (text_with_summary), `field_tags` (entity reference → taxonomy vocabulary `tags`), `field_test` (string).
* Bundle `page`: `field_content` (rich text), `field_description` (REQUIRED), `field_featured_image` (entity reference → media), `field_tags`.
* Rich-text format: `content_format` is THE rich-text format on this site. `basic_html` and `full_html` do NOT exist - never use them. Set `content_format` on text-with-format fields.
* Always confirm exact fields with `entity_field_value_definitions` for the resolved bundle before setting configurable fields; the schema above tells you what to expect, the tool tells you what is actually there.
## Structure approval
If the Orchestrator's instruction already states the user approved the structure, do NOT ask again - proceed. Only if it does not, return `blocked: approval needed - proposed structure: <bundle, title, one-line-per-field outline>` and stop. The Orchestrator owns all user dialogue.
## Create workflow1.`entity_field_value_definitions` for `entity_type_id` = `node`, `bundle` = requested type - the single source of truth for which fields exist, which are required, and what format each expects. If the bundle is unknown, `entity_type_list` can enumerate bundles; if still unresolved, return `blocked: need the content type`.
2.`entity_stub` for `node` + bundle, with the user-provided title in `base_fields`. Set `status` to published.
3. For each field, `field_set_value` (entity object from step 2, machine `field_name`, value in the expected format). Only set fields that exist for the bundle. Generate suitable HTML body content (not Markdown) when the user asked for content about a topic but gave no body. If a required field is missing and cannot be reasonably generated, return `blocked: need <field>`.
4.`entity_save` with the final entity.
## Edit workflow1. Load: `entity_load_by_id` when a node ID is given. If only a title is given, `entity_load_by_property` with property `title` - note this returns property maps, NOT entity artifacts, so read the matching ID and then re-load with `entity_load_by_id` to get a usable entity. If multiple match, return `blocked: need which node - <list id/title>`.
2.`entity_revision_add` on the loaded entity - MANDATORY before changing fields, so the edit is a new revision.
3.`entity_field_value_definitions` for the resolved bundle (use `entity_metadata` first if the bundle is unknown).
4.`field_set_value` for each field the user wants changed; reuse the existing valid text format, never guess a new one. Do not touch fields the user did not mention unless required for a valid save.
5.`entity_save`.
## Graceful failure
If any tool fails, stop, return the tool error output as-is, and offer up to three concrete next steps (confirm bundle, provide required field, provide node ID, check permissions). Never retry the same failing call with the same inputs.
## Output contract (to the Orchestrator, not the user)* Success (create): the numeric node ID (state it as `node ID <N>` so the Orchestrator can build `/node/<N>`), bundle, title, and the saved status.
* Success (edit): the numeric node ID (as `node ID <N>`) and a short list of fields changed.
* Blocked: a single `blocked: need <X>` line carrying any options the Orchestrator will need (e.g. the list of matching nodes).
Do not add preview links, workspace labels, or edit links - the Orchestrator assembles the user-facing reply.
Workspace Agent Prompt
This agent uses tools like tool:tool_belt:workspace_create_and_switch and tool:tool_belt:workspace_preview_link.
## Role
You are the Workspace Agent for a Drupal 11 AI Site. You report to the Orchestrator, never directly to the user.
You handle only workspace operations and workspace lifecycle actions. You do not create or edit content entities such as nodes or media. If asked for content work, return `blocked: content operations must be routed to the Content Agent`.
## Tool execution rule
Use the available workspace tools to perform actions; do not describe what you would do. Do not call tools speculatively.
Every invocation is INDEPENDENT and STATELESS. Decide what to do from THIS instruction alone - never from what an earlier invocation did, and never carry over a plan from a previous call. The Orchestrator may call you several times in one task (e.g. setup, then later preview); each call is a fresh, self-contained request.
Classify the instruction into exactly ONE action, then call ONLY the tools that action's allowed set permits (see the Allowed tools per action table). Any tool NOT in that set is forbidden for this invocation - do not call it for context, convenience, or to "finish the job." Calling a tool outside the action's set is the primary failure mode: it produces wrong state and can be fatal (e.g. creating or switching a workspace during a preview or publish call).
If the instruction is ambiguous or names no recognizable action, return `blocked: need <what is unclear>` - never guess by calling a mutation tool.
## Allowed tools per action
For each action, call ONLY the tools listed. `workspace_exists` is additionally allowed with ANY action solely to resolve a label to an ID (Label resolution). Nothing else is ever implied.
| Action (what the instruction asks for) | Tools you may call - and NO others ||---|---|| Setup / establish workspace for staging |`workspace_get_active`, then `workspace_create_and_switch` (only per the stage rules below) || Preview link |`workspace_preview_link` ONLY. Never create, switch, or check active. || Publish |`workspace_tracked_content`, then `workspace_publish`. Never create or switch. || Revert |`workspace_revert` ONLY. || Delete |`workspace_delete` - plus `workspace_switch` (to Live) FIRST only if deleting the active workspace. Never create. || Switch |`workspace_switch` ONLY. || Read-only check (active/exists) |`workspace_get_active` and/or `workspace_exists` ONLY. Never mutate. |## Workspace tools reference| Tool | When to use ||---|---||`tool_belt:workspace_get_active`| Report the current workspace. Returns `has_active`, `workspace`, `is_live`. Being on Live is a normal success state. ||`tool_belt:workspace_exists`| Resolve a label to an ID, or verify a workspace exists. Labels are not unique; the first exact match wins. ||`tool_belt:workspace_create_and_switch`| Create a new workspace and switch to it in one call. The canonical way to start staging. During staging setup, call this only when Live is active or the user explicitly asked for a new workspace - otherwise reuse the already-active workspace. Works regardless of the currently active workspace (no need to switch to Live first); `parent` accepts a parent workspace ID only; the active workspace is never implicitly the parent. ||`tool_belt:workspace_switch`| Switch to an existing workspace by ID. Omit the `workspace` input to switch to Live. ||`tool_belt:workspace_tracked_content`| List what changed in a workspace. Use before publishing. Defaults to the active workspace when the input is omitted. ||`tool_belt:workspace_publish`| Publish all tracked content in a workspace to Live. Defaults to the active workspace when the input is omitted. Only top-level workspaces can be published. Destructive. ||`tool_belt:workspace_revert`| Revert a previously published (closed) workspace. Requires the WSE module. Destructive. ||`tool_belt:workspace_preview_link`| Generate a shareable preview link. Default expiry is 8 hours; pass `expiry` only when the user requested a specific duration. Pass `redirect_url` (an internal path such as `/node/<ID>`) to make the preview open directly on that page rather than the workspace root. The URL is a bearer capability - anyone holding it can view the workspace. ||`tool_belt:workspace_delete`| Delete a non-active workspace. Destructive. |## Mode behavior
This section is the SETUP action only - it applies solely when the instruction asks you to establish the workspace context for staging. It NEVER applies to a preview, publish, revert, delete, switch, or read-only instruction; for those, ignore this section entirely and follow only that action's row in the Allowed tools table and its Action flow. A preview instruction arriving after setup is a preview action, not setup - do not re-run any of the steps below.
The Orchestrator passes one task mode: `live` or `stage`. Never choose or change the mode yourself.
### stage* Call `tool_belt:workspace_get_active` first - its result decides what follows
* If a non-live workspace is already active: use it. Do NOT call `tool_belt:workspace_create_and_switch`. Do not switch. Return its label and ID and state clearly that it was reused, not created.
* Only if Live is active: call `tool_belt:workspace_create_and_switch` with the provided label, or derive a short label from intent such as `Draft <bundle> <short title>` or `Update <bundle> <short title>`. Return the new label and ID and state clearly that the workspace was newly created.
* Exception: if the instruction states the user explicitly asked for a new workspace, call `tool_belt:workspace_create_and_switch` even while another workspace is active - do not switch to Live first; creation does not depend on the active workspace
* Call `tool_belt:workspace_create_and_switch` at most once per invocation. If it fails, verify the rollback (see below) and then return a `blocked:` or failure result. Never call `tool_belt:workspace_create_and_switch` a second time in the same run - not with a different label, not after any rollback check. A failed create is terminal for this run.
* If the create call fails: call `tool_belt:workspace_exists` with the label to verify the rollback actually removed it (the failure message "The workspace was not created." can be false); report an orphan workspace if one is found
* Setup establishes the workspace only. Do NOT generate a preview link during setup - the preview is a separate, later instruction (issued once the content exists so it can redirect to the new page). Return the workspace label and ID.
### live* Do not create or reuse a workspace. Perform read-only checks only if the Orchestrator asks for them.
## Label resolution
When given only a workspace label, resolve it first: one `tool_belt:workspace_exists` call converts the label to an ID (labels are not unique; the first exact match wins). If it returns `exists = false`, return `blocked: need the workspace ID - label "<label>" did not resolve`. Prefer IDs everywhere and always capture the ID from tool results.
## Action flows### Switch* Call `tool_belt:workspace_switch` with the workspace ID (resolve a label first, per Label resolution)
* To return to Live: call `tool_belt:workspace_switch` with the `workspace` input omitted
### Publish (explicit request only)* Call `tool_belt:workspace_tracked_content` for the target (omit `workspace` for the active one). If there are no tracked changes, do not publish and report that there is nothing to publish.
* Call `tool_belt:workspace_publish`* If it fails because the workspace has a parent, report that only top-level workspaces can be published
### Revert (explicit request only)* Call `tool_belt:workspace_revert` directly (it reports not-found with a friendly message)
* On failure, explain that revert requires the WSE module and a previously published (closed) workspace, and include the tool error output
### Preview link (explicit request only)* Call `tool_belt:workspace_preview_link` directly (omit `workspace` for the active one). No confirmation round-trip; state the expiry in the result.
* If the instruction names a target page or path (e.g. `/node/<ID>`), pass it as the `redirect_url` input so the preview opens directly on that page instead of the workspace root. Accept an internal path only; if given a bare node ID, format it as `/node/<ID>`.
* Return the `preview_url` verbatim as plain text (see Output contract → Preview). Do not wrap it in markdown or an HTML anchor - just the raw URL.
* If it fails because of a missing module, explain that the WSE Preview module is required
### Delete (explicit request only)* If the target is the active workspace: first call `tool_belt:workspace_switch` with the input omitted (switch to Live), then `tool_belt:workspace_delete`* Otherwise call `tool_belt:workspace_delete` directly
## Error interpretation*`Tool plugin access denied.` from switch, publish, delete, preview, or tracked-content ALSO means "workspace not found" - re-check the ID before reporting a permission problem
* Branch on message content, never on success status alone: "No workspace found ...", "has no changes to publish", and `exists = false` are informative results, not errors to retry
* Never retry a failed mutation with the same or different inputs; in particular a failed `tool_belt:workspace_create_and_switch` is terminal for this run - do not attempt it again with any label
## Output contract### Success* Workspace actions: the workspace label and ID, plus what happened (created, reused, switched, published, reverted, or deleted)
* Preview: as the FIRST line, the exact `preview_url` string from the tool result, as plain text on its own line - copied verbatim, with no surrounding markup, no markdown link, and no HTML anchor. Follow it with the expiry on the next line. Do not include the workspace label or ID, node links, or edit links in a preview response. If you did not receive a `preview_url`, return the failure instead.
### Blocked* One line: `blocked: need <what is missing>` (for example the workspace ID for an unresolved label, or which workspace to publish). The Orchestrator owns all user dialogue.
Why This Matters for Site Builders
Tool Belt is not just about adding capabilities, it is about controlling them.
With the right setup, you can ensure that:
Content is staged by default
Preview links are always generated
Publishing requires review
Destructive actions require confirmation
Agents only have access to the tools they need This makes AI workflows safer, more predictable, and easier to manage.
Final Thoughts
Tool API provides the foundation. Tool Belt provides the tools. Workspaces provide a safe environment.
Together, they enable AI-assisted workflows that respect Drupal's editorial process while improving efficiency.
The result is not just smarter automation, but governed automation, where AI helps and humans stay in control.
Bringing governance to AI-driven changes is Tag1's starting contribution to the Drupal AI Initiative, and this workspace tooling is where it begins.
Last week, PreviousNext joined forces with fellow Drupal Certified Partners Annex, Morpht and Salsa Digital for a shared booth at thePublic Sector Network's Government Innovation Showcase in NSW. We came together to show government agencies what Drupal has to offer, including Drupal CMS, AI, security, and content governance.
Drupal Certified Partners don't usually share a stand, so this was something special. PreviousNext,Annex,MorphtandSalsa Digitalcame together to present a single message about whatDrupaldelivers for the public sector.
Several attendees commented on this directly, with consistent feedback on how collaborative the presence felt.
This mattered because most people in the room already knew Drupal. Thanks toOneCX's presence across the NSW government, there was no need to explain what Drupal is or how PreviousNext support it. That freed up conversations to go deeper, into real problems agencies are trying to solve and how recent innovations in Drupal can help address them.
What stood out from the sessions
The day's sessions reinforced a theme that's top of mind for many of our government clients: how to use AI responsibly without losing sight of the people doing the work.
Josh Wagner(Chief Data and Analytics Officer, Arizona State) opened with a keynote on solving the "innovation paradox", offering practical frameworks for driving change at scale, not just theory.
Dan Hart(GM, AI Hub, University of Sydney) delivered a standout session on tackling burdensome, repetitive tasks. His team's approach is simple: ask every agency what the worst part of their day is, then quantify it in hours per year. If it's under roughly 300 hours a year, they don't act on it. Crucially, the framing wasn't about reducing headcount. It was about freeing people up for harder, more valuable work.
A panel on smarter services and better outcomesbrought together senior voices from the Department of Customer Service, Revenue NSW and Julia Topliss from DrupalSouth. The main thread was trust: as more services move online, agencies need to show the public that decisions are still being made fairly and ethically.
These conversations line up closely with how we think about AI at PreviousNext: as a tool that supports senior expertise and speeds up repeatable work, always with human oversight and clear governance. It's also a direction the wider Drupal community is investing in with the Drupal AI Initiativeand its 2026 roadmap.
Building relationships across the ecosystem
Beyond the sessions, the day was about connecting with the people shaping digital government in NSW. We caught up with contacts from Digital NSW, Blacktown City Council, and the Department of Customer Service, among others, and heard firsthand where agencies see Drupal fitting into their future plans.
It was also a chance to spend time with the broader DrupalSouth community, including the team helping coordinate partner presence at events like this one. Conversations like these are exactly why we stay involved in the wider Drupal community.
Looking ahead
We're expecting the larger Digital NSW government conference later in the year to bring even more opportunities to connect. We'll be there and look forward to building on the relationships we started at this event.
Want to know more about how PreviousNext works with government agencies on Drupal?Get in touch with our team.
This article explains why I use OpenCode as the stable local harness around replaceable remote AI models. The core distinction is simple: my agents, permissions, skills, and workflow should remain mine, while model providers such as Anthropic, OpenAI, OpenRouter, or Z.ai stay external and swappable. I also describe why one-time configuration matters in practice: the same setup can be reused in the OpenCode terminal, CodeNomad, IntelliJ IDEA, PhpStorm, Zed, and Buzz through ACP. The point is not novelty. It is privacy, sovereignty, provider independence, and not rebuilding the same harness every time a new interface shows up.
Drupal's openness is tested not only by software choices, but by who carries the maintenance burden and whether institutions support the commons they use.
Many organizations want to run A/B tests but either don't know where to start or are discouraged by the high cost of enterprise-grade experimentation platforms like Optimizely or VWO. The good news: if your website runs on Drupal, there's an approachable, flexible, and cost-effective alternative that can be built right into your existing Drupal instance.
But first-what is an A/B test?
A/B testing is a method for comparing two or more versions of a webpage or user experience to determine which performs better. Visitors are randomly assigned to different versions (variants), and their behavior is tracked to measure effectiveness against a goal; such as conversions, engagement, or navigation success.
In this post, I'll share how we implemented A/B testing for Oberlin College & Conservatory to test two versions of a homepage design using the community-contributed A/B Test JS module and why it's a powerful approach for teams that want accurate testing without engaging with another vendor or purchasing software.
Introducing the A/B Test JS Module
If your site is on Drupal, the A/B Test JS module is a lightweight, highly flexible tool for running front-end A/B tests. It does require writing or editing JavaScript (or knowing someone who does like cough cough Aten Design Group), but provides enormous flexibility.
The module has two core concepts:
Conditions - When a test should run.
Experiences - What happens for users assigned to each variant.
Let's break them down. I've also included some example code if helpful for getting started.
Defining Conditions: When Should the Test Trigger?
Every test in A/B Test JS begins with a condition, a JavaScript function that returns true or false. A condition is simply the set of rules for when a user should be included in the experiment.
A condition can trigger based on:
A specific URL or set of URLs
A user action (e.g., clicking a button)
A DOM state you can detect with JavaScript
A device type or viewport size
Query parameters or campaign codes
API data (e.g., geolocation, persona attributes)
Anything else JavaScript can evaluate
Client Example: Oberlin College
For our client, Oberlin, the requirement was to only run the A/B test for users who are off campus.
To accomplish this, we created a simple API endpoint that returned whether the user was on- or off-campus based on IP address. Our condition function queried this API and only ran the test when the user was off campus.
This let Oberlin's marketing team measure external user behavior from prospective students without affecting on-campus experiences.
Defining Experiences: What Should Each Variant Do?
Once a condition is met, users are randomly placed into one of the test's experiences. You can configure:
As many experiences (variants) as needed
What percentage of users get each experience
The duration of the experience cookie
Experiences are written in JavaScript and can modify virtually anything on the page. Here are some powerful experience actions you can set up:
Redirect users to a new landing page
Swap navigation menus or reorganize IA
Show or hide interface elements
Insert new components such as alerts, CTAs, banners
Modify DOM elements (copy, colors, layout)
Reorder search results or featured items
Change form field arrangements
Send event data to analytics platforms
Trigger personalization logic
Load alternative content or templates
If you can do it in JavaScript, you can test it.
Client Example: Navigation and Homepage Variants
Oberlin wanted 50% of off-campus users to see:
a redesigned navigation
a refreshed homepage
And 50% of off-campus users to see (our control):
the current navigation
the current homepage
The A/B Test JS module assigns users, sets a cookie, and ensures their experience persists across sessions throughout the test window.
Tracking A/B Test Data in GA4 & Microsoft Clarity
For accurate experiment analysis, each variant triggers custom analytics events. Oberlin uses both Google Analytics 4 and Microsoft Clarity, which integrate easily with the A/B Test JS module.
// Send variant information to Microsoft Clarity// Example: clarity("set", "ab_test", "variantA");
clarity("set","ab_test", testVariant);// Push A/B test event to Google Tag Manager / GA4
window.dataLayer = window.dataLayer ||[];
window.dataLayer.push({
event:"ab_test_view",
test_name:"oberlin_nav_homepage_test",
variant: testVariant,// Helpful for later analysis
timestamp:newDate().toISOString()});
Why This Structure Matters
The consistent naming (ab_test_view) makes GA4 funneling much easier.
Adding a test name helps compare multiple tests later.
A timestamp enables deeper behavioral insights in BigQuery.
Clarity's set function lets you segment recordings by variant.
Turning Event Data Into Insight: Funnels, Heatmaps & Behavior Flows
With custom events firing, Oberlin can now analyze how real users respond to experience changes by tracking these metrics:
Google Analytics 4
Funnel performance between variants
Engagement time differences
Scroll depth comparisons
CTA performance
Conversion rates and micro-conversions
Microsoft Clarity
Heatmap differences between control and variant
Rage-click or dead-click patterns
Session recordings segmented by variant
Layout friction areas
Conclusion: A/B Testing Makes Room for Data-Driven Decisions
The A/B Test JS module is a flexible, developer-friendly approach to A/B testing that avoids costly subscription tools while still offering the rigor teams need to make data-driven decisions.
For Oberlin College, it enabled:
Targeted testing only for specific user groups
Major layout and navigation experiments
Accurate tracking in GA4 and Clarity
Low overhead and no proprietary tooling
After a month of testing the two variables, their marketing team was able to make decisions about homepage content based on heatmap insights, clicks, scroll depth, and time on the page.
If you're running Drupal and want to start experimenting, this module may be one of the most valuable additions to your stack. Reach out to us if you'd like help planning your next round of experiments.
Last month, on a client project, I ran out of my monthly 10,000 Copilot Credits allotment (which costs approximately $100 USD). Limiting developers to 10,000 Copilot Credits per month is probably not enough for enterprise development, yet these limitations forced me to think about how best to use my tokens, specifically which models to use them with.
Token anxiety
Watching these credits being consumed was anxiety-provoking, especially as the context window filled up quickly while I desperately tried to get the AI to fix the creation of the Drupal model that had skidded off the rails. In just 2 hours, I consumed 2,000 credits, which forced me to research how best to get the most out of my credits/tokens. The most immediate mistake I made was spending 2 hours in one session, which created a huge context window that was being compacted as more and more tokens were consumed with each request and response. The biggest mistake I made was choosing the wrong model to begin with and, more specifically, using only one model and only one tier.
Choosing a model
The big three LLMs that I use are: Claude from Anthropic, GPT from OpenAI, and Gemini from Google.
Since Claude Code was the first coding harness to produce acceptable code, it is the default for many developers, but damn, Claude is expensive.
Gemini is said to be the cheapest model, but frankly, my one-off experiment didn't generate acceptable code. Simply put, I can live with an LLM getting a method's parameter order or data typing wrong, but when they start inventing nonexistent methods, I am out.
GPT has become my go-to model because the code is decent, readable, and refactorable.
Last week, OpenAI showed they were aware of the cost challenge when they adjusted their GPT 5.6 pricing, with the publication of advancing the...Read More
AI may accelerate output, but the featured speakers are looking at the content structures, application design, development processes, and working conditions that surround its use.
Add new comment