跳到正文
原文
Google AI:DEV 作者专属(RSS)· Artifizer·· 5 小时前AI 评分33

API 工程化已成熟,事件、MCP 工具与 AI 智能体等契约怎么办?

APIs Are Well Engineered. What About Everything Else?

AI 导读

软件工程已把 API 的契约、版本、鉴权和弃用流程做得相当成熟,但事件、配置对象、工作流、MCP 工具、AI 智能体、提示词和策略等同样是契约,却仍靠各自临时机制处理。文章以事件平台为例,提出用类似编程语言继承的“类型派生”让具体事件继承通用事件契约,从而统一校验、生产与读取授权,并应对 Event v1 到 v1.1 新增 origin 字段后的历史数据兼容问题。

正文

Software engineers have learned how to engineer APIs quite well.

For an API, we routinely think about:

  • contracts
  • schemas
  • versioning
  • backward compatibility
  • breaking changes
  • authentication
  • authorization
  • ownership
  • documentation
  • discovery
  • lifecycle and deprecation

If I expose:

POST /customers

I can define exactly:

  • what the request looks like
  • what the response looks like
  • who can call it
  • which version is being used
  • whether a change breaks existing clients

We have OpenAPI, OAuth, API gateways, schema validation, versioning rules, compatibility tools, and mature practices around all of this.

But modern software is no longer made only of APIs.

We also exchange and operate on:

  • events
  • configuration objects
  • user and tenant settings
  • workflows
  • serverless function contracts
  • MCP tools
  • AI agents
  • prompts
  • policies
  • extension manifests
  • plugin-defined data

These artifacts are contracts too.

But they are often still handled by separate, ad-hoc mechanisms.


Imagine you are designing an event platform

Assume the platform is not just a REST API.

Events may arrive through:

  • gRPC
  • Kafka
  • WebSockets
  • internal queues/SDKs

So we cannot simply say:

OpenAPI validates everything for us.

The event system itself needs to understand the contracts.

Suppose the platform has events like:

Event
├── Application X Activated
│
├── MCP Agent Y
│   ├── Work Started
│   └── Work Completed
│
└── Audit Event
    ├── User Authentication Failure
    └── User Logged In

The platform itself defines:

Event
Audit Event
User Authentication Failure
User Logged In

while external applications, agents and vendors may introduce their own event types, e.g.:

MCP Agent Y -> Work Started

Let's consider a common Event

Every event should contain common fields:

{
  "timestamp": "...",
  "tenantId": "...",
  "eventType": "...",
  "payload": "..."
}

Call this schema:

Event

Now audit events need some additional common fields:

...
"payload": {
  "userId": "...",
  "ipAddress": "...",
  "auditData": "..."
}

So we define:

Audit Event

as a more specialized form of:

Event

Then specific audit events add their own fields.

For example:

User Authentication Failure

could additionally require:

...
"auditData": {
  "error": "Invalid password"
}

while:

User Logged In

does not need an error.

So the relationship is:

Event
  timestamp
  tenantId
  eventType

    ↓

Audit Event
  userId
  ipAddress

    ↓

User Authentication Failure
  error

and:

Event
    ↓
Audit Event
    ↓
User Logged In

The important idea is simple:

A more specific event inherits (or specifies) the contract of the more general event above it.

So a User Authentication Failure must satisfy:

Event fields
+
Audit Event fields
+
Authentication Failure fields

Likewise, User Logged In must satisfy:

Event fields
+
Audit Event fields
+
User Logged In fields

Here we can call this type derivation, by analogy with traditional programming languages.

It is similar to inheritance in programming languages, but applied to schemas and data exchanged between independent systems.


How do we express this relationship?

Programming languages solve this naturally.

You might write:

class Event:
    timestamp: datetime
    tenant_id: str

class AuditEvent(Event):
    user_id: str
    ip_address: str

class AuthenticationFailure(AuditEvent):
    error: str

But now these types are not Python classes.

They are JSON objects traveling between different systems.

JSON Schema can describe each structure.

But we also want the platform to know:

Authentication Failure derives from Audit Event

Audit Event derives from Event

That relationship becomes useful for much more than validation.


How should the event system validate an incoming event?

Suppose this arrives:

{
  "timestamp": "2026-10-03T10:00:00Z",
  "tenantId": "customer-12",
  "eventType": "user_authentication_failure",
  "userId": "42",
  "payload": {
      "ipAddress": "10.1.2.3",
      "auditData": {
           "error": "Invalid password"
      }
   }
}

The event system should be able to answer:

  • What type is this event?
  • Where is its schema?
  • Is that schema registered?
  • Does this object satisfy the schema?

Who can produce an event?

Now security appears.

Suppose:

Application X

is allowed to produce:

Application X Activated

Should it also be allowed to emit:

User Logged In

Probably not.

Otherwise any application could impersonate the authentication subsystem simply by sending JSON with the correct fields.

We may want rules like:

Application X
    may produce
        Application X events

Authentication Service
    may produce
        Audit authentication events

This is similar to API authorization.

With APIs where every resource type is explicit, we might say:

service A can call /billing/*

For events, we want to say:

service A can produce this family of event types

Who can read an event?

The same applies to consumers.

Perhaps a normal application can read:

- Application X Activated
- Job Started
- Job Completed

but it should not see audit events.

An administrator might be allowed to read:

all Event types

while an auditor might receive:

Audit Event
and everything derived from Audit Event

This becomes especially useful as the system grows.

If tomorrow the platform adds:

- Password Changed
- API Token Created
- Administrative Permission Changed

we don't want to rewrite every security policy.

They are all still:

Audit Event

So a rule defined for the parent type can apply automatically to the new derived types.


What happens when the common Event contract changes?

Suppose millions of events have already been stored.

The original schema was:

Event v1

- timestamp
- tenantId
- eventType

Later we decide that every event should also have origin as v1.1:

- origin

Now the questions become much more practical:

  • What happens to all historical events that don't contain origin?
  • Are they still valid?
  • Should the new field be optional?
  • Can it later become required?
  • When an API returns an old event, which schema should it claim to follow?
  • Should the API transform old events and populate origin?
  • Can new consumers safely read both versions?
  • How long must the platform support v1 for ingest?
  • Which producers still send v1?
  • Can we automatically determine whether v1.1 is compatible with v1?

This is not very different from evolving an API.

But the artifact is an event type, not an API endpoint resource.

The same compatibility problem still exists.


What if Audit Event changes?

Now imagine adding:

- sessionId

to Audit Event.

That change should affect:

- User Authentication Failure
- User Logged In
- Password Changed

but it should not affect:

- Application X Activated
- MCP Agent Work Started

So the platform needs to understand the hierarchy:

Event
├── Application X Activated
├── MCP Agent Work Started
└── Audit Event
    ├── User Authentication Failure
    └── User Logged In

That allows it to answer:

  • Which schemas may be affected by this change?
  • Which stored data uses previous versions?
  • Which producers need to be upgraded?
  • Which consumers may break?

External vendors make this harder

Now imagine that the platform supports third-party applications.

The platform defines its core event hierarchy:

Event
└── Audit Event
    ├── User Authentication Failure
    └── User Logged In

But Vendor B installs an integration and introduces:

Event
└── Integration B Connection Failed

Vendor C installs an AI product and introduces:

Event
└── AI Agent C Started

Both should still be valid platform events.

Conceptually:

Event
├── Application X Activated
├── Integration B Connection Failed
├── AI Agent C Started
└── Audit Event
    ├── User Authentication Failure
    └── User Logged In

Now several new questions appear.

Who owns:

Integration B Connection Failed

How do we prevent another vendor from registering a type with the same name?

  • Which application is allowed to emit it?
  • Can Vendor B extend one of the platform's common types?
  • What happens when Vendor B publishes version 2?
  • Which consumers accept version 1, version 2, or both?

This is where simple local names stop being sufficient.


Events are not special

Events are just an easy example.

The same problem appears with many other data types.

Consider platform-managed data such as:

- User Settings
- Subscription Settings
- VM Settings
- Application Settings
- Integration Settings
- ...

The platform may want to provide:

storage
validation
versioning
access control
discovery

while allowing installed applications and integrations to add their own attributes or specialized types.

One integration may define additional subscription settings.

Another vendor may introduce additional VM properties.

A plugin may add user-level settings.

You immediately get the same questions:

  • Who owns this data type?
  • What does it derive from?
  • Which version is stored?
  • Who can read it?
  • Who can modify it?
  • Can another vendor reference it?
  • Can an extension add fields without breaking existing consumers?
  • What happens to old stored objects after the schema evolves?

MCP tools have the same problem

Suppose an MCP tool declares:

Input:
    Repository

Output:
    Code Analysis

What exactly is:

Repository
  • Is it a local name?
  • A GitHub repository?
  • A generic source-code repository type?
  • Which vendor defines it?
  • Which version?

Can the tool accept a specialized subtype such as:

GitHub Repository

Can it accept every type derived from:

Repository

Then security questions appear:

  • Who is allowed to invoke this tool?
  • Which types of data may be passed into it?
  • May a third-party MCP tool receive sensitive configuration?
  • Which output types is it allowed to create?
  • Can its output be passed safely to another agent?

Again, these look surprisingly similar to the problems we already solved around APIs.


The missing common layer

APIs have:

identity
contracts
versions
compatibility
ownership
security
references

But for many other artifacts we repeatedly create separate mechanisms:

event registry
schema registry
config registry
agent registry
MCP registry
function registry
workflow registry

Each eventually needs some version of:

name
owner
schema
version
references
permissions
compatibility

Perhaps these are not completely separate problems, and they need a shared type system underneath them.


This is the idea behind Global Type System

This is what we are exploring with Global Type System — GTS.

GTS is an open specification and open-source project for identifying and referencing data types and their instances.

The specification is language-independent, with JSON and JSON Schema as its primary current focus.

A GTS type identifier looks like:

gts.acme.billing.events.invoice_created.v1~

Its structure is:

gts.<vendor>.<package>.<namespace>.<type>.v<major>[.<minor>]~

For example:

vendor      = acme
package     = billing
namespace   = events
type        = invoice_created
version     = v1

The trailing ~ identifies a type.

GTS can also identify instances of types.

The project is developed openly, and the specification repository includes its license and implementation rules.


Why put all of this into the identifier?

Because one identifier can carry several useful pieces of information.

1. Type or instance identity

A GTS identifier can identify:

a schema/type

or:

a concrete instance of that type

So the same identification model applies to both definitions and data.


2. Ownership

The identifier encodes:

vendor
package
namespace
type

For example:

gts.vendor_b.integration.events.connection_failed.v1~

and:

gts.vendor_c.ai.events.agent_started.v1~

can coexist without colliding.

The ownership is visible directly from the identifier.


3. Versioning

Version is part of the type identity:

gts.acme.billing.events.invoice_created.v1~
gts.acme.billing.events.invoice_created.v1.1~
gts.acme.billing.events.invoice_created.v2~

This gives tooling something deterministic to reason about when schemas evolve.


4. Type inheritance and derivation

GTS identifiers can be chained to represent that one type derives from another.

Conceptually:

Event
    ↓
Audit Event
    ↓
User Authentication Failure

The derived type keeps the relationship to its parent types.

That means tooling can determine not only:

this is User Authentication Failure

but also:

this is an Audit Event
this is also an Event

This relationship can then be used for validation, discovery, subscriptions, or policy decisions.


5. Access control

Because identifiers have predictable namespaces, security policies can work with exact identifiers or wildcard patterns.

For example:

gts.acme.billing.events.*

could represent all billing event types belonging to the package.

A policy system could therefore express rules such as:

Service A may produce:
    gts.acme.billing.events.*

User B may read:
    gts.acme.public.events.*

Auditor may read:
    Audit Event and its derived types

The policy engine itself is separate from GTS.

GTS provides the structured identity on which the policy can operate.


6. References between data types

Types also need to refer to other types.

This is effectively the schema equivalent of a foreign key.

For example:

Invoice
    references
        Customer

or:

Agent
    references
        Tool

Instead of referencing a vendor-specific local schema name, the reference can point to a globally identified type.

That becomes important when different organizations publish schemas independently.


The larger idea

Programming languages gave us type systems inside applications.

API engineering gave us strong contracts, versions, compatibility rules, and security between services.

But modern platforms now exchange much more than API requests.

They exchange:

events
settings
configs
workflows
tool contracts
agent data
function definitions
policies
extension data

Those artifacts increasingly cross application, vendor, and organizational boundaries.

They need many of the same properties APIs already have.

GTS is an attempt to provide a common type identity and relationship layer for that broader software ecosystem.

It is an open specification and open-source project:

https://github.com/GlobalTypeSystem/gts-spec

https://github.com/GlobalTypeSystem

I'm particularly interested in how people building event platforms, agent ecosystems, MCP infrastructure, schema registries, plugin systems, and extensible SaaS products deal with these questions today.

Are we dealing with separate problems?

Or are we repeatedly rebuilding fragments of the same missing type system?

来源:Google AI:DEV 作者专属(RSS) · dev.to