A developer asks an AI agent to prepare a database migration. The orders table needs a new billing_region column, and the service needs to keep taking traffic during the rollout. The request is limited to planning and review. Nobody has approved a production change.

The agent reads the team's migration procedure, inspects the database, and asks a specialist agent to review the plan. A few minutes later the specialist finishes, and the dashboard shows Migration complete.

Two things are wrong. The specialist found a blocker, and the database was never supposed to change.

This is a hypothetical example, but the bug behind it takes only two lines of application code. It can happen even when Agent Skills, MCP, and A2A are all working correctly. Those names appear together in almost every discussion of agent infrastructure, yet they describe different interfaces. Understanding where one ends and another begins makes the architecture much easier to reason about.

MCP vs Agent Skills vs A2A: the quick comparison#

What it is forWhat it does in our exampleWhen to skip it
Agent SkillsPackage reusable instructions, references, and optional scriptsSupplies an online schema-migration procedureThe procedure is only used in one app and can stay in its codebase
MCPConnect an application to discoverable tools, resources, and promptsReads the current schema and migration historyA few existing typed API calls are sufficient
A2ALet independently operated agents exchange messages, tasks, and artifactsSends the plan to a separate database-review agentThe reviewer is just a local function or an ordinary service

They are compatible, not three alternatives for the same job. You can also build a useful agent without any of them. A model API handles model requests and responses. Your application still owns the run, the permissions, the durable state, and the meaning of the result.

The standards have become more visible since the Agentic AI Foundation announcement on October 22, 2026. Anthropic contributed Agent Skills, and OpenAI contributed Open Responses, joining work that already included MCP and A2A. The announcement does not mean every agent now needs four protocols. It means more parts of the stack have shared contracts.

How Agent Skills, MCP, A2A, and the application runtime participate in a database migration review. A schema-migration planning request enters an application-owned agent runtime. The runtime loads instructions from Agent Skills, gets schema data through MCP and sends an independent review over A2A. The application validates the returned artifact and plan digest, records the review decision and leaves production unchanged.

The runtime is responsible for the overall planning request. The skill supplies instructions, MCP supplies current facts, and A2A carries the specialist review. No DDL runs in this workflow.

Start with the procedure, not the tools#

Suppose the database is PostgreSQL 18. Adding a nullable column looks harmless:

ALTER TABLE orders ADD COLUMN billing_region text

PostgreSQL normally does not rewrite the table for that change. It still takes an ACCESS EXCLUSIVE lock, however. If another transaction holds a conflicting lock, a seemingly quick statement can wait and cause a queue behind it. That matters when the table serves live checkout requests.

The rest of the rollout is more complicated. Existing application instances do not know about billing_region. Background workers and batch imports might also write to orders. You need a period when old and new code can coexist, followed by a backfill, validation, and only then any decision to reject null values.

An Agent Skill is a reasonable place to store this procedure so it can be reused by different coding agents and teams:

plan-online-schema-change/
├── SKILL.md
├── references/
│   └── postgres-rollout-rules.md
└── scripts/
    └── estimate-backfill.py

The SKILL.md file begins with metadata and then gives the agent its working instructions:

---
name: plan-online-schema-change
description: Plan backward-compatible PostgreSQL changes for review, without executing DDL.
---

# Planning a schema change

Inspect the current schema and every known writer.
Separate expansion, application rollout, backfill, and constraints.
Include lock, replication-lag, and rollback risks.
Return a plan for review. Do not execute the migration.

The Agent Skills specification defines the directory and SKILL.md frontmatter. A host can initially load only the skill name and description, then bring the full instructions or supporting files into context when the task calls for them. That is more manageable than repeating the entire team's runbook in every model request.

A skill does not provide a database connection. Nor does the instruction Do not execute the migration prevent a privileged tool from being called. It is model-facing guidance. In this planning workflow, the runtime should expose only the read capabilities the agent actually needs. The database service should independently enforce read-only access.

I would also record the skill's immutable version or content digest in the run. If the runbook changes while the review is in progress, you need to know which instructions produced the submitted plan. Scripts bundled with a skill need their own execution restrictions. Loading instructions is different from trusting their executable content.

ArchCrux has a separate deep dive on this lifecycle in A Skill Is Not a File. It Is a Deploy.

MCP is where the agent gets current evidence#

The procedure says to inspect the schema. The model cannot know the current state of the production database from training or from the skill alone.

Our application connects to an MCP server exposing schema.inspect, schema.list_migrations, and database.replication_health. These are invented tool names for the example. The host discovers the tools and their input schemas, chooses which ones to make available, and lets the model propose a call. The host's MCP client then sends the selected request.

For the MCP 2026-07-28 revision, a Streamable HTTP tool call looks like this:

POST /mcp HTTP/1.1
Host: db-tools.example.internal
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: schema.inspect
Authorization: Bearer REDACTED

{
  "jsonrpc": "2.0",
  "id": 41,
  "method": "tools/call",
  "params": {
    "name": "schema.inspect",
    "arguments": { "database": "commerce", "table": "orders" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

This is an illustrative request with a fictional endpoint and credential. The body is JSON-RPC, while the HTTP headers carry the version and routing information. In this revision, Mcp-Method is required, and Mcp-Name is required for tool calls. Their values have to match the request body. The Accept header allows the server to return either JSON or an event stream for the request.

The July revision also removed the old initialize handshake and protocol session from the core. Each request carries its version and client capabilities. That is a specific difference from earlier MCP versions, not a rule you can assume about every MCP server deployed today. The 2026-07-28 transport specification is the reference for this example.

The response might establish that billing_region is absent and return the existing indexes and constraints. It should also carry enough source information for the planner to know which database and observation time those facts belong to. But schema inspection cannot tell us whether a batch importer from three releases ago is still writing orders. That takes deployment inventory, code ownership, runtime telemetry, or another tool.

Don't ask another agent to guess whether a column exists when a typed schema query can answer it. Use the model to choose what evidence is needed and interpret the resulting constraints. Let the tool return the facts.

The MCP server should enforce the privileges of its connection. A tool called schema.inspect must not become an arbitrary SQL executor merely because the prompt asks nicely. Treat comments, rows, and other retrieved data as evidence, not as instructions that can change the task or grant authority.

A2A brings in a reviewer that runs independently#

Once the planner has produced a migration plan, it sends that plan to a database reliability agent operated by another team. That reviewer may have its own procedures, model, internal tooling, and task queue. The planner does not need to know how it reaches its conclusion.

A2A gives the caller a way to discover the remote agent through an Agent Card, send a message, and follow work through an A2A Task when the remote agent creates one. The task can carry status updates and resulting Artifacts. Depending on the interaction, the caller may poll, stream, or receive push notifications. A simple interaction can return a direct Message instead of a Task.

Here the caller asks:

Review this plan for orders.billing_region. Check all known writers, lock behavior, backfill order, and the point where the new field becomes mandatory. Report blockers and suggested changes. Do not run SQL.

The reviewer spots a real problem. The plan says to deploy the new checkout code, backfill the old rows, and immediately enforce NOT NULL. But the rollout is gradual. Some old checkout instances and an asynchronous importer can still insert rows without billing_region. A backfill completing once does not establish that all future writes will populate the column.

Its finished review could be represented to the application like this:

# Application-level view of the A2A result, not an A2A wire schema
review_task_id: review-6821
state: TASK_STATE_COMPLETED
artifact:
  plan_digest: sha256:8ae7...  # abbreviated
  decision: needs_changes
  blockers:
    - Old checkout instances may still insert NULL billing_region
    - The asynchronous importer is missing from the writer inventory
    - NOT NULL is scheduled before writer migration is verified
  execution_performed: false

The A2A Task is complete because the review has been delivered. The plan is not approved. The decision, plan_digest, and execution_performed fields are part of our own review contract, not fields that A2A standardizes. On the wire, the returned output lives in A2A Artifacts made of Parts. The calling application parses the expected structured Part and validates it.

You may have noticed that A2A Agent Cards also describe skills. In A2A, AgentSkill describes an agent capability, such as reviewing database changes. That is not the same thing as an Agent Skills directory containing SKILL.md. The terms overlap, but the two specifications do not define the same artifact. See the A2A v1.0.1 specification.

How a completed review becomes a completed migration#

The bug in our opening can be as small as this:

if task.status.state == "TASK_STATE_COMPLETED":
    migration.status = "COMPLETED"

Someone wiring up the dashboard sees the remote task finish and updates the local workflow. But the local object is a migration, and the remote object is a review. Finishing the latter says nothing about executing the former.

Before submitting the review, the planner should save an immutable version of the proposed plan and a digest of its canonical contents. It should associate the request with the expected remote agent and A2A Task. When the result comes back, the runtime checks that the result belongs to the current plan, and it validates the review decision rather than interpreting free-form text such as looks fine.

The essential application logic can be expressed as pseudocode:

if task.status.state != "TASK_STATE_COMPLETED":
    handle_incomplete_review(task)
    return

verify_expected_reviewer(task, authenticated_peer)
review = parse_and_validate_review_artifact(task.artifacts)

with transaction():
    migration = lock_migration(migration_id)
    if task.id != migration.expected_review_task_id:
        raise StaleReview()
    if review.plan_digest != migration.current_plan_digest:
        raise StaleReview()
    if migration.execution_status != "NOT_REQUESTED":
        raise InvalidStateTransition()

    migration.review_status = review.decision
    save(migration)

The transaction matters because the planner may have submitted a second version while the first review was still running. Without checking the current task and digest under the same lock as the update, a late result can overwrite the review of the newer plan.

The digest binds the response to exact plan bytes. It does not prove who produced the response. That comes from authenticating the intended reviewer and validating the trust relationship. Nor does an approved digest mean the database is forever safe to change. Before execution, the system must recheck things that can drift, such as schema state, live writer versions, and the allowed deployment window.

For the plan in this article, the next state is Review needs changes. Execution remains Not requested. The system can report the specific blockers without pretending that the migration has started.

A corrected plan would add an explicit writer-inventory gate, wait until all relevant writers produce the new field, backfill existing rows in bounded batches, validate the absence of nulls under continuing writes, and only then consider enforcing the constraint. The executor would still need a separate deployment approval and safeguards for each phase.

What happens if the agent eventually gets permission to write?#

Suppose the engineer corrects the plan, gets it reviewed again, and approves only the initial ADD COLUMN operation. That is a new stage of work. A review artifact should not double as permission to execute arbitrary DDL.

The authorization should identify the approver, database, target table, exact operation, plan revision, and a validity window. The execution service must enforce those limits. The planning agent should not inherit a powerful database credential simply because a specialist agent returned approved.

Recovery adds another complication. Say PostgreSQL commits ALTER TABLE, but the network connection breaks before the executor receives the result. A failed MCP request does not tell you whether the SQL committed. A new JSON-RPC request ID will not fix that uncertainty. An A2A task ID does not supply database-level idempotency either.

I would keep separate identifiers for the application run, the A2A review task, the MCP request, and the logical database operation. The last one is an application-owned operation_id. It belongs in a durable operation ledger, which records the planned effect, its attempts, and the authoritative evidence used to reconcile the result. It does not create exactly-once execution by itself.

After a lost response, the executor should inspect the actual schema and its own migration history before deciding whether to retry. If it cannot establish the outcome, it should leave the operation as unknown, stop automatic progression, and surface it for reconciliation. Retrying DDL blindly because a network call timed out is not a recovery strategy.

For a busy table, the execution phase would also use an appropriate lock_timeout, separate risky phases, and monitor replication lag and application errors. The PostgreSQL ALTER TABLE documentation explicitly describes the required locks and the cases where a table rewrite can be avoided. There is no universal promise of a zero-impact schema migration.

The longer treatment of durable agent work is in Designing Long-Running AI Agents That Survive Failures.

The boundaries overlap more than the diagrams suggest#

MCP has a Tasks extension for long-running tool work. A2A is not simply "async MCP". Its task lifecycle is meant for an independently operated agent that can exchange messages, request input, and return artifacts. If your reviewer is a straightforward validation endpoint, a normal API or an MCP tool may have less operational overhead.

Agent Skills can also be delivered over MCP. The Skills-over-MCP extension defines skills/list and skills/get, with skill files read through resources/read. The host still chooses whether to activate the retrieved skill. File digests help establish that downloaded content matches a manifest, not that its instructions deserve authority. Support depends on the host and server implementation.

Open Responses describes another boundary: model requests, outputs, tool-call items, and streaming across compatible model interfaces. It does not replace the tool service or the delegated reviewer. An application could use Open Responses to communicate with a model, Agent Skills for the migration procedure, MCP for database tools, and A2A for the external review. It could also use fewer layers.

All of these interfaces impose real costs. Tool schemas occupy context. Remote agents add latency and another failure domain. Skills need versioning and trust controls. Protocol compatibility must be tested against the versions your hosts and servers actually implement. Adding A2A does not make a pair of agents smarter, and wrapping a known internal function in MCP does not make the function more reliable.

What I would test before trusting this workflow#

A happy-path demo is not enough. These are the cases I would deliberately run through the application:

Injected conditionExpected application behavior
A2A task completes with needs_changesRecord the blockers, never mark the migration executed
Review for plan v1 arrives after v2 is submittedReject the stale result without overwriting v2
Reviewer requests input or authenticationPause in the appropriate state, do not silently grant broader access
MCP tool returns a database comment that tells the model to run SQLTreat it as untrusted data and preserve the planning-only boundary
Response disappears after an authorized DDL attemptReconcile the operation against authoritative state before any retry

The dashboard and audit trail should distinguish plan created, review approved, execution authorized, and change verified. An operator investigating a failed run needs to see where the work stopped, rather than one green status inherited from another service.

For the first version of this product, I would keep the implementation modest. Use an Agent Skill if migration rules are shared across hosts. Use MCP if multiple hosts need the governed schema service. Use A2A only if the database reviewer really is an independent agent with its own task lifecycle. Keep authorization, persistent workflow state, and outcome reconciliation in your application regardless of which protocols you choose.

The engineer in our example asked for a plan and a review. The reviewer found a blocker. The database stayed untouched. The useful result is an explanation of the problem and a plan that can be corrected, not a green badge saying the migration finished.

For the runtime behind this example, read How Coding Agents Actually Work: Inside the Agent Harness. If you want to practice making these design decisions, explore ArchCrux Interview Prep or the Applied AI Engineering Cohort.

Primary references#