Governed Agent Ledger POC — Drupal + Dolt + Beads¶
Status: POC playbook
Purpose: prove that Drupal can govern agent work stored in the existing Beads/Dolt ledger without creating a parallel work schema, a custom Drupal module, or a new orchestration platform.
This playbook defines the proof. Beads track the work required to execute it.
1. POC claim¶
The proof is intentionally small:
Agent writes work on a Dolt task branch
↓
Drupal surfaces the existing Bead record and branch diff
↓
Human reviewer approves or rejects
↓
Drupal emits a governed approval event
↓
Agent runtime performs the Dolt merge
↓
Drupal reflects the merged state
The second proof introduces a real conflict:
Agent A edits bead 1234 on branch A
Agent B edits bead 1234 on branch B
↓
merge A succeeds
merge B conflicts
↓
Drupal renders the conflict as a review decision
The POC is successful only if the same Beads records remain usable by agents operating inside and outside Drupal.
2. Authority boundary¶
Keep the systems separate.
Drupal owns governance¶
Drupal owns:
- human and agent identity exposed to the product surface
- permissions
- reviewer authority
- approval/rejection state exposed to humans
- review UX
- evidence visible to the reviewer
- product workflow
Drupal does not own the engineering work ledger and does not directly perform Dolt merges.
Beads/Dolt owns durable work state and versioned substrate¶
Beads/Dolt owns:
- the existing agent work records
- task branches
- diffs
- merge history
- conflicts
- durable work state used by Gas City and agents outside Drupal
Do not create a replacement Drupal work schema.
Gas City / runtime owns execution¶
The runtime owns:
- creating task branches
- agent execution
- writing Beads
- validating approval events
- executing the merge
- handling merge failure/conflict
- returning execution result
Use native Gas City primitives before Bluefly code: Beads, Orders, Formulas, hooks, Polecats, packs, external clients, MCP, and JSON command contracts where applicable.
GitLab remains source/CI authority¶
Any code or configuration change required to build the POC follows the normal GitLab path and shared CI authority. Do not use this POC as a reason to add project-local CI.
3. Negative-ownership gate¶
For every implementation decision use:
DELETE
→ REUSE EXISTING
→ GAS CITY NATIVE
→ DRUPAL CORE / CONTRIB
→ OTHER UPSTREAM
→ CONFIGURE
→ PATCH / OVERLAY
→ THIN ADAPTER
→ CUSTOM CODE only on proven gap
Target outcome:
CUSTOM_DRUPAL_MODULES=0
CUSTOM_PHP=0
CUSTOM_WORK_LEDGER=0
CUSTOM_AGENT_ORCHESTRATOR=0
CUSTOM_MODEL_ROUTER=0
CUSTOM_EVENT_PLATFORM=0
If one of these becomes non-zero, record the exact capability gap that forced it. An honest thin adapter is preferable to hiding custom ownership.
4. Proposed stack¶
Use the existing Beads schema as-is.
Required proof candidates¶
- Drupal 11 core multiple database connections
- Dolt using the MySQL wire protocol
- existing Beads database/schema
- External Entities v3
- xnttsql SQL storage plugin
- dbxschema and appropriate database driver support
- ECA
- Entity Webhook + Broadcast, only if Step 0 proves it fires for external entities
- Key for webhook signing material if Entity Webhook is used
Conditional / fallback pieces¶
- Http Client Manager only if Entity Webhook cannot handle the external-entity approval event; pin a version known to contain the relevant security fix before use
- Views Database Connector only for a read-only simplification where entity permissions/events are not needed
- ai_agents and Tool API only for the optional “agent inside Drupal” demonstration
- Gas City external messaging (
extmsg), MCP, or existing Tool API integration should be evaluated before a custom Drupal↔Gas City bridge
Do not install every candidate up front. Add only what the proof currently requires.
5. Decisions locked for the POC¶
Branch granularity: per task¶
Use one Dolt branch per task.
Why:
- the branch has a natural beginning and end
- approval corresponds to one bounded unit of work
- a task branch can be merged, rejected, retried, or deleted
- per-agent branches live too long
- per-session branches create unnecessary merge noise
Naming should map cleanly to the Bead/task identifier.
Drupal reads committed/main ledger state, not active working branches¶
Drupal's normal ledger view reads the authoritative main branch.
Review views read the diff between a task branch and main.
Do not point normal Drupal product views at an agent's in-progress branch.
The agent resolves conflicts¶
Drupal decides approve/reject and displays conflict evidence.
The agent/runtime resolves the conflict after rejection or conflict detection.
Do not build a general conflict-resolution editor in Drupal for the POC.
Beads schema remains authoritative¶
Do not create a new “agent memory” or “agent task” schema in Drupal.
The entire point is to compose on the existing work ledger.
6. Step 0 — gating experiment¶
Do this before Dolt integration or UI work.
Question¶
Does Entity Webhook Broadcast fire when an External Entity is updated?
This is currently an assumption, not a proven fact in this playbook.
Minimal experiment¶
- Use a disposable Drupal 11 site.
- Configure any second SQL database/table. Dolt is not required yet.
- Install only the minimum External Entities/database storage pieces and Entity Webhook Broadcast required for the test.
- Surface one row as an external entity.
- Configure an outbound test endpoint.
- Edit the row through Drupal.
- Observe whether the POST fires.
- Verify payload, signature behavior if enabled, retry/log behavior, and whether the event can be conditioned on the changed approval/status value.
Gate¶
EXTERNAL_ENTITY_UPDATE=PASS
ENTITY_WEBHOOK_FIRED=YES
DELIVERY_LOG_CREATED=YES
CONDITION_FILTER_WORKS=YES
If all required behavior passes: use Entity Webhook.
If it does not: test the smallest ECA + outbound HTTP fallback. Do not create custom PHP until the contrib fallback is actually proven insufficient.
Nothing else in the POC should depend on this assumption before Step 0 closes.
7. Step 1 — prove Dolt mechanics independently¶
Before Drupal is involved, prove the version-control behavior at the SQL/runtime level against the existing governed Beads/Dolt topology.
Do not start another Dolt server merely for convenience if the authoritative environment already provides the required logical database.
Prove:
BEADS_DATABASE_REACHABLE=YES
TASK_BRANCH_CREATED=YES
BEAD_WRITTEN_ON_TASK_BRANCH=YES
MAIN_UNCHANGED_BEFORE_MERGE=YES
DIFF_QUERY_WORKS=YES
MERGE_WORKS=YES
MAIN_CONTAINS_CHANGE_AFTER_MERGE=YES
Also prove the negative path:
TWO_BRANCHES_EDIT_SAME_RECORD=YES
FIRST_MERGE=PASS
SECOND_MERGE=CONFLICT
CONFLICT_DATA_QUERYABLE=YES
Use native Dolt branch/diff/merge mechanisms. Capture exact commands and queries as execution evidence in the POC Bead, not as an alternate hand-maintained runbook.
8. Step 2 — connect Drupal to the ledger read surface¶
Use Drupal's second database target to reach the Dolt/Beads database through its MySQL-compatible endpoint.
Rules:
- credentials come from the approved environment/1Password path; never literals in tracked settings
- Drupal receives only the database privileges needed for the POC
- the default read connection points at the authoritative/main ledger state
- do not give Drupal a general-purpose merge credential
Proof:
DRUPAL_SECOND_CONNECTION=PASS
READ_FROM_BEADS_MAIN=PASS
NO_DRUPAL_MERGE_CAPABILITY=YES
CREDENTIAL_LITERAL_IN_REPO=NO
Treat branch-addressing through connection configuration as a hypothesis to verify against the deployed Dolt version/topology rather than assuming it from this document.
9. Step 3 — surface Beads without inventing a schema¶
Configure an External Entity type representing the existing Bead record.
Start read-only.
Map only the fields required for the demo:
- Bead/task ID
- title/summary
- status
- branch/task reference if present or derivable through existing data
- timestamps / actor fields required for evidence
Only after read behavior is proven, allow the smallest update required for governance. Do not give Drupal generic write access to Bead content.
Preferred POC boundary:
Agents/runtime own work content.
Drupal reviewer owns approval decision.
If the existing Beads schema does not contain the exact approval/status representation needed, do not alter the ledger casually. First determine whether approval is better represented as existing Beads state, a linked governance record, or an event outside the work-row mutation. The POC must not corrupt native Beads semantics merely to make Drupal convenient.
Proof:
BEAD_ENTITY_LIST=PASS
BEAD_ENTITY_DETAIL=PASS
BEADS_SCHEMA_REUSED=YES
NEW_LEDGER_SCHEMA=NO
10. Step 4 — render the review diff¶
Create the smallest review surface that can answer:
What did this agent/task branch change compared with main?
Use Dolt's native diff/query surface and expose the structured result through Drupal.
The review screen should show, at minimum:
- task / Bead ID
- task branch
- changed record/field
- before value
- proposed value
- change type
- actor/task evidence
- approve
- reject with reason
Do not build a generalized diff framework. One Beads table and one useful review screen are sufficient for the POC.
Proof:
TASK_BRANCH_DIFF_QUERY=PASS
DRUPAL_DIFF_VIEW=PASS
BEFORE_VALUE_VISIBLE=YES
AFTER_VALUE_VISIBLE=YES
11. Step 5 — governance and approval semantics¶
Do not claim Drupal Content Moderation is being used unless the entity type actually supports the required revision/moderation semantics. For this POC, assume it does not until proven otherwise.
Implement approval with existing Drupal permissions plus configuration/ECA where possible.
Minimum roles:
agent / machine principal
reviewer
administrator
Minimum properties:
- unprivileged actor cannot approve
- reviewer can approve/reject
- identity of reviewer is recorded
- rejection includes a reason visible to the runtime/agent
- approval does not directly execute SQL merge from Drupal
Proof:
UNPRIVILEGED_APPROVAL=DENIED
REVIEWER_APPROVAL=PASS
APPROVER_ID_RECORDED=YES
REJECTION_REASON_SUPPORTED=YES
DRUPAL_DIRECT_MERGE=NO
Machine identities must be real least-privilege principals. Do not borrow Thomas's identity and do not invent secret references.
12. Step 6 — emit approval as an event¶
Preferred path, if Step 0 passes:
Drupal approval
↓
Entity Webhook Broadcast
↓
signed payload
↓
existing runtime / Gas City-facing receiver
Payload should contain identifiers, not broad authority:
bead_id
task_branch
approval_decision
approver_id
timestamp
correlation_id
The runtime must independently verify that the request is valid and authorized before merge.
If Entity Webhook fails the Step 0 gate, use the smallest proven ECA + HTTP path.
Do not create a new event bus.
Where Gas City can consume the event through an existing native mechanism (Order, hook, external client, existing API, MCP/tool boundary), use that before a Bluefly receiver service.
Proof:
APPROVAL_EVENT_FIRED=YES
SIGNED_OR_EQUIVALENT_AUTHENTICATED=YES
DELIVERY_EVIDENCE=YES
CORRELATION_TO_BEAD=YES
CUSTOM_EVENT_PLATFORM=NO
13. Step 7 — runtime merge¶
Drupal never performs DOLT_MERGE directly.
The runtime receives the approved task identifier, validates it, resolves the task branch, and performs the native merge through the authorized runtime identity.
Target flow:
approval event
↓
authorization validation
↓
Gas City Order / governed runtime action
↓
Dolt merge
↓
result recorded in Beads / runtime evidence
↓
Drupal reflects resulting main state
On conflict:
- do not force merge
- classify the task as requiring resolution
- capture conflict evidence
- return rejection/conflict state to the review surface
- route a new/child Bead only if native Beads semantics require additional work tracking
- let the agent resolve and resubmit
Proof:
HMAC_OR_EVENT_AUTH_VERIFIED=YES
AUTHORIZED_RUNTIME_IDENTITY=YES
MERGE_EXECUTED_BY_RUNTIME=YES
MAIN_UPDATED=YES
DRUPAL_REFRESH_SHOWS_RESULT=YES
14. Step 8 — two-agent conflict demo¶
This is the most important demonstration after the happy path.
- Start from the same main state.
- Create task branch A and task branch B.
- Have two disposable agents/Polecats change the same Bead field differently.
- Present both as independent reviewable work.
- Approve A and merge it.
- Approve or attempt B.
- Observe native Dolt conflict rather than silently overwriting A.
- Surface both conflicting values and the conflict state in Drupal.
- Reject/return B to the agent for resolution.
- Agent resolves on its branch and resubmits.
Success is not “Drupal automatically resolves the conflict.”
Success is:
a concurrent agent write becomes explicit governed work instead of a silent race condition.
Proof:
TWO_AGENT_BRANCHES=YES
SAME_RECORD_CHANGED=YES
FIRST_MERGE=PASS
SECOND_MERGE=CONFLICT
SILENT_OVERWRITE=NO
CONFLICT_VISIBLE_IN_DRUPAL=YES
HUMAN_DECISION_POSSIBLE=YES
AGENT_CAN_RESUBMIT=YES
15. Step 9 — optional inside-Drupal agent path¶
Do this only after the outside-agent path is complete.
Outside-agent path is the canonical first proof:
Gas City/Polecat/agent
→ Dolt task branch
→ Beads
→ Drupal review
→ approval event
→ runtime merge
Then, if useful for the Drupal demonstration, prove a Drupal-hosted agent using existing ai_agents + Tool API capabilities.
Do not let this optional path change the ledger architecture. The same Beads records and task-branch semantics must remain valid for both execution locations.
16. Gas City native-first integration¶
Before creating any adapter, check native Gas City capabilities.
The POC should prefer:
- existing agent definitions and harness configuration
- existing upstream/provider abstraction for model choice
- Polecats for disposable work
- Orders for when/where work fires
- Formulas only where a reusable multi-step method is actually proven
- Beads for durable work
- hooks/events instead of polling
- packs/patches instead of copied agent definitions
gc ... --json/ JSON-schema contracts for machine consumption rather than parsing human terminal output- external messaging, MCP, or existing Tool API endpoints before a custom Drupal↔Gas City bridge
Do not build the factory beside Gas City. Run this real POC through it.
17. Bead decomposition for the POC¶
Create/reconcile these as the minimum seed graph. Existing matching Beads win; do not duplicate them.
EPIC: GOVERNED_AGENT_LEDGER_POC
│
├── 0. VERIFY_EXTERNAL_ENTITY_WEBHOOK_GATE
│
├── 1. PROVE_DOLT_BEADS_BRANCH_DIFF_MERGE
│
├── 2. CONNECT_DRUPAL_TO_BEADS_READ_SURFACE
│
├── 3. SURFACE_BEADS_AS_EXTERNAL_ENTITIES
│
├── 4. BUILD_STRUCTURED_DIFF_REVIEW_VIEW
│
├── 5. PROVE_REVIEWER_PERMISSION_AND_DECISION
│
├── 6. EMIT_GOVERNED_APPROVAL_EVENT
│
├── 7. MERGE_THROUGH_AUTHORIZED_RUNTIME
│
├── 8. PROVE_TWO_AGENT_CONFLICT_FLOW
│
└── 9. OPTIONAL_DRUPAL_HOSTED_AGENT_PATH
Do not fully pre-plan every implementation task. Let each proof bead create linked child work only when execution discovers a real gap.
18. Execution order and stop conditions¶
Gate A — webhook/event feasibility¶
Do not proceed to full UI/configuration until Step 0 answers the external-entity event question.
Gate B — native Dolt mechanics¶
Do not involve Drupal until task branch, diff, merge, and conflict are proven directly.
Gate C — read model¶
Do not add approval mutation until Drupal can reliably render the existing Beads records and task diff.
Gate D — governance¶
Do not connect approval to merge until permission boundaries and actor identity are proven.
Gate E — happy path¶
Do not build the conflict demonstration until one complete approve→event→runtime merge round trip passes.
Gate F — ownership review¶
At the end, evaluate every added dependency/configuration/adapter:
KEEP
REPLACE_WITH_EXISTING
UPSTREAM
DELETE
19. Required end-to-end receipt¶
Do not claim the POC works until a fresh run proves:
AUTHORITATIVE_BEADS_LEDGER_REUSED=YES
NEW_WORK_SCHEMA=NO
TASK_BRANCH_CREATED=YES
AGENT_WROTE_BEAD=YES
MAIN_UNCHANGED_PRE_APPROVAL=YES
DRUPAL_SECOND_DB_CONNECTION=PASS
DRUPAL_LISTS_BEADS=YES
DRUPAL_DIFF_VIEW=PASS
UNPRIVILEGED_USER_CANNOT_APPROVE=YES
REVIEWER_APPROVED=YES
APPROVER_ID_RECORDED=YES
APPROVAL_EVENT_FIRED=YES
EVENT_AUTH_VERIFIED=YES
DELIVERY_EVIDENCE=YES
RUNTIME_PERFORMED_MERGE=YES
DRUPAL_PERFORMED_MERGE=NO
MAIN_CONTAINS_APPROVED_CHANGE=YES
DRUPAL_SHOWS_MERGED_STATE=YES
TWO_AGENT_CONFLICT_CREATED=YES
SECOND_MERGE_CONFLICTED=YES
SILENT_OVERWRITE=NO
CONFLICT_VISIBLE_TO_REVIEWER=YES
AGENT_RESOLUTION_AND_RESUBMIT=PASS
CUSTOM_DRUPAL_MODULES_WRITTEN=0
CUSTOM_PHP_WRITTEN=0
CUSTOM_LEDGER_SCHEMA=0
CUSTOM_EVENT_PLATFORM=0
CUSTOM_AGENT_ORCHESTRATOR=0
THOMAS_USED_AS_ROUTER=NO
THOMAS_CREDENTIAL_BORROWED=NO
WITNESS_VERIFICATION=PASS
If a zero becomes one, name the precise gap and decide whether the adapter is worth owning.
20. POC demo script¶
Demo 1 — approval¶
- Show main Bead state in Drupal.
- Launch/identify a real task branch.
- Agent changes one Bead on that branch.
- Refresh Drupal review queue.
- Show before/after values.
- Human clicks Approve.
- Show delivery/event evidence.
- Show runtime merge.
- Refresh Drupal main view and show the approved value.
Demo 2 — conflict¶
- Reset to known state.
- Agent A and Agent B edit the same Bead on separate task branches.
- Approve/merge A.
- Attempt B.
- Show native conflict.
- Show both values in Drupal.
- Reject/return B with reason.
- Agent resolves and resubmits.
The story is not “Drupal stores agent memory.”
The story is:
Drupal governs shared agent work without owning the ledger, and Dolt turns concurrency into a reviewable merge decision instead of a silent race condition.
21. Source design notes and unverified assumptions¶
This playbook incorporates the design from the Google Doc 09 BUILD SPEC — governed agent ledger, contrib only and the Bluefly native-first Gas City operating model.
Important assumptions that must be proven rather than repeated as facts:
- Entity Webhook Broadcast behavior on External Entities
- exact Drupal 11 compatibility/current release status of every selected contrib project
- exact xnttsql write semantics required by the approval design
- exact Dolt branch-addressing syntax in the deployed version/topology
- whether approval should mutate an existing Beads status field or be represented through another native governance/event mechanism
- which native Gas City event/API primitive is the thinnest receiver for the approval event
These are POC gates, not reasons to invent custom infrastructure in advance.
22. Completion definition¶
The POC is complete when one real agent task can be created on a Dolt task branch, reviewed through Drupal, approved by an authorized human, merged by the governed runtime, reflected back in Drupal, and then repeated with a deliberate two-agent conflict that becomes visible reviewable state rather than silent corruption.
The strongest completion state is:
REAL_BEADS_REUSED=YES
DRUPAL_GOVERNS=YES
DOLT_VERSIONS_AND_MERGES=YES
GAS_CITY_EXECUTES=YES
HUMAN_APPROVAL_VISIBLE=YES
CONFLICT_BECOMES_DECISION=YES
CUSTOM_MODULES=0
If those are true, stop. Do not generalize the POC into a platform until a second real use proves what deserves reuse.