One Task Table to Rule Them All
The backlog from my last post had one line I couldn’t ignore: status transitions weren’t implemented yet. Tables, views, the model-driven app – all done. The missing piece was the Dataverse Agent Task contract itself. In other words, the part that lets Power Platform and Azure talk to each other.
That wasn’t a bug. It was a missing seam.
Without an explicit contract, two teams can’t build independently. They can’t test independently either.
Remember my post about project decomposition? I already traced one receipt across that seam. That post showed the handshake working. This one shows what makes it work: one generic Dataverse table, structured so either side can build against it before the other exists.
Everything from here on out claims a task through exactly this table.
Why One Table, Not Six
Building the Agent Task table, I had two things in mind. First: decouple the work. Power Platform and Azure shouldn’t need to know how the other side operates internally. Second, and just as important: keep a trace. Every handover between two independently working systems should be visible – not buried in a log file only a developer can read. This table is the Dataverse Agent Task contract in its rawest form.
That second goal matters more than it sounds. When an agent picks up a task, I want to see, as a human, exactly what got handed over and exactly what came back. Not “trust me, it worked.” An actual record. That’s how you build trust in an agentic system – not by hoping the agent behaved, but by being able to check.
Here’s what I drafted in the spec:
The table carries task type, status, input JSON, result JSON, error JSON, and error details. Every task fits this same pattern. Take Analyze Document: the document to analyze goes into the input JSON, the extraction result comes back in the result JSON. The task type tells an agent which one to pick up and how to process it. The status field tracks where the task stands.
Decoupling gave me the freedom to build both sides independently. But it also raised the bar on the contract itself, especially once AI started processing these tasks. Agents need clear rules for reading the input JSON and writing back a result JSON that actually matches what’s expected.
Building a Strong Contract
Decoupling Power Platform from Azure solved one problem. It didn’t solve the next one: how does an agent know what shape the input JSON should take for “Analyze Document” versus “Suggest Trips”? I could hardcode that knowledge into every worker. Or I could put it in the table, completing the other half of the Dataverse Agent Task contract.
Running a New Spec Cycle
I ran the full GitHub 🌱 SpecKit workflow on this: specify → clarify → plan → tasks → analyze → implement. This was the second spec in the repository, same discipline as the first, just one level deeper.
Define JSON schemas for input and result data per task type, so an agent – or any other independent component – can create, interpret, and validate payloads without guessing. That was the whole ask.
Specify to implement took less time than writing this post. That’s the part I still don’t fully trust, and the part that makes the workflow worth repeating.
Specification Becomes Reality
The plan didn’t stop at the data model. It sketched the review form as plain text, right there in the plan document:
+----------------------------------------------+
| [Identity] |
| Name Catalog Key |
| Task Type Payload Direction |
+----------------------------------------------+
+----------------------------------------------+
| [Version] |
| Contract Version Contract Lifecycle |
| Major Version Minor Version |
| Published On |
+----------------------------------------------+
Here’s what actually shipped:
Same sections, same order, same names. Not a coincidence. That’s the plan doing its job.
Injecting Prepared Test Data
The spec named nine task types up front: Analyze Document, Ensure Currency, Suggest Trips, Enrich Calendar, Prepare Export, Send Notification, Rename File, Check Duplicate, and Other. For every one of them, AI drafted an input, result, and error schema. Then it injected the whole set into a test environment to see if it actually held up.
The how wasn’t improvised either. Early drafts of the plan leaned on the Dataverse MCP server to inject and re-inject those test schemas, a natural fit for a conversational, one-off record. But a regression check needs to produce the exact same result on every run, and that’s not what MCP was built for. So the real importer upserts by a fixed key instead: same input, same output, every time, with nothing in Dataverse left half-changed by a partial run.
Twenty-seven rows. Nine task types times three directions. Every one of them reviewable by a human, without opening a code editor.
The contract isn’t just something I described in a spec anymore. It’s something I can point at.
Understanding the State Machines
Ask an AI agent to plan a feature and you’ll get code eventually. Ask it to plan a contract, and something more useful shows up first: a state machine, spelled out before anything gets built.
That’s the Agent Task lifecycle, exactly as SpecKit’s planning step laid it out. Draft, Ready, Claimed, Running, Succeeded or Failed. One cycle per unit of work, fast, and gone once it’s done.
The schema catalog got its own diagram, on a completely different clock: Draft, Current, Supported, Deprecated, Retired. Slower, deliberate, one lifecycle per versioned agreement instead of per task.
Here’s what makes this useful. I didn’t have to reverse-engineer either lifecycle later, from finished code. I could read it, as the architect, before a single table existed – and catch the one mistake that matters most at this stage: mixing the two up. Retire a schema the moment its replacement ships, and every task still running against the old version fails for no operational reason at all.
Two diagrams, drafted before the build, readable by a human, adjustable in five minutes if something’s off. That’s the real value of asking AI to plan the state machine, not just the schema.
What Testing Found
Two lifecycles, both documented before the build. That didn’t make the plan perfect on the first pass.
Reviewing the finished schema catalog form surfaced two small gaps nobody had actually specified. The technical EM prefix – there for the constitution’s naming rules, not for humans – had leaked straight into the UI captions. And the new entity never got a proper icon, so it sat in the sitemap looking like every other generic table.
None of that was a failure. That’s what testing is for: catching what the spec didn’t say, then looping back through spec, plan, and implementation until it does. It’s only perfect when a reviewer catches details this small too.
AI wrote the spec, planned the form, and built it – fast, in one pass. Catching the leaked prefix and the missing icon was still my job. That’s the actual split in this workflow: AI moves the implementation forward, I make sure it still makes sense to the person using it.
Summary
Two tables did more work than I expected going in. Together, that’s the Dataverse Agent Task contract: the table and the catalog that governs it.
The Agent Task carries the actual exchange – task type, status, input JSON, result JSON. The Agent Task Schema carries something more important: the rules an agent has to follow before its output counts as valid. Together, they’re not just a data model. They’re how I enforce discipline on something that doesn’t naturally have any – an AI agent that will happily produce whatever shape you didn’t explicitly forbid.
That’s the part I didn’t fully appreciate when I started. Decoupling Power Platform from Azure was the obvious win. The real one turned out to be smaller and sharper: giving every task type a contract an agent can be held to, and giving me, as the human, a place to check it.
Both tables are built. Both lifecycles are documented. And testing already proved the GitHub 🌱 SpecKit loop works: spec, plan, implementation, review, correction, repeat.
What’s still missing is someone to actually claim a task and prove the rules hold under real AI output, not just a fixture.







