Blog • Open Responses
After Open Responses joins the AAIF, which type, arguments, and status checks come before you replay a tool-call item to another model?
On October 22, 2026 the Agentic AI Foundation made Open Responses a hosted project. The spec packs one model turn into items: message, function_call, and function_call_output are the portable core; a type with a provider prefix belongs to that implementation.
Before you send a logged item to another model, check three fields locally. A colon in type means it is not a portable function_call. In the spec examples arguments is a JSON string: parse it, then check the tool JSON Schema. If status is not completed, do not append function_call_output.
Yesterday's Bedrock structured outputs covers the shape of model text. Chat Completions often stores arguments as a string too, in a different envelope—see AI Agent and JSON Schema. A schema pass still does not mean you may run the tool—see the permission decision. This article covers only the Open Responses item.
- Portable item types are message, function_call, or function_call_output.
- A type that contains a colon is a provider extension; do not replay it as a portable tool call.
- arguments must be a string; validate the parsed object against the tool schema, including fields such as region.
- Stop on in_progress or incomplete. Write function_call_output only when status is completed, and the call_id must match.
Keep this in mind: AAIF accepted a shared item shape. One JSON record can still belong to a single provider. The sections below follow type, then arguments, then status, and show which field stops a replay.
On October 22 the shared item moved under open governance
At AGNTCon and MCPCon that day, AAIF announced four new projects. Open Responses, contributed by OpenAI, sits beside MCP and A2A under Linux Foundation governance. The announcement is at the AAIF announcement. The same day also brought Anthropic Agent Skills, Hugging Face OpenEnv, and Alibaba OpenSandbox. This article does not cover those catalogs or the skill format. It only covers Open Responses item JSON.
The same day, an AWS open-source post said Amazon Bedrock supports the Responses API and is participating in the spec. See the AWS open-source post. Field meanings follow the Open Responses specification. Non-streaming bodies are application/json, and items are distinguished by type. The practical question: a function_call is already in the log; before you switch providers or replay it the next day, which three fields should stop the next request.
Which fields does a replayable function_call need?
A function_call that can move to the next check carries at least type, id, name, call_id, status, and arguments. The arguments value is a string; the tool parameters live inside that string. The sample below uses region apac and is checked again with the same schema.
{
"type": "function_call",
"id": "fc_001",
"name": "get_latest_sales_report",
"call_id": "call_123",
"status": "completed",
"arguments": "{\"region\":\"apac\"}"
}
After the tool runs on your side, the next request appends function_call_output, not a Chat Completions tool_calls array. The spec continuation example keeps the same call_id and adds output. If the server later returns that result as an item, id and status are filled in then.
{
"type": "function_call_output",
"call_id": "call_123",
"output": "sales report for apac"
}
Why does a colon in type block a cross-provider run?
The spec lets an implementation add its own items, but type must carry a provider slug prefix, for example openai:web_search_call. That record can stay in the original session and go back to the same provider. The spec says not to assume it is portable to another Open Responses server. Local rule: a colon marks the item namespaced, and you do not emit function_call_output.
{
"type": "openai:web_search_call",
"id": "ws_001",
"status": "completed",
"action": { "type": "search", "query": "apac sales report" }
}
| type you see | Replay on another provider? | Local action |
|---|---|---|
function_call | Continue to the next check | Check arguments and status next. |
message | Can go into input | Treat it as conversation text, not a tool result. |
openai:web_search_call | Not portable | Keep it with the original provider, or split it out of the cross-provider log. |
Why must arguments be a string before the schema check?
The streaming event response.function_call_arguments.delta appends text before the object is finished, so stored arguments is a string, not an object. A log that puts an object in arguments fails the next parse or is rejected by another implementation. Confirm the value is a string, then json.loads, then the tool JSON Schema. The schema below allows only apac, emea, and amer.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["region"],
"properties": {
"region": {
"type": "string",
"enum": ["apac", "emea", "amer"]
}
}
}
The string below parses, but region is global, which is not in the enum. The gate should return arguments_schema, and the tool should not run.
{
"type": "function_call",
"id": "fc_009",
"name": "get_latest_sales_report",
"call_id": "call_129",
"status": "completed",
"arguments": "{\"region\":\"global\"}"
}
| arguments you see | Gate result | Local action |
|---|---|---|
| A string whose object has region apac | ok | Run the tool, then write function_call_output. |
| arguments is an object, not a string | arguments_type | Reject. Turn it back into a string before parse. |
| The string parses, but region is global | arguments_schema | Do not run. Write the validation error back; do not guess a region. |
Why skip the tool result when status is not completed?
The spec treats each item as a state machine. in_progress means output is still arriving. incomplete is terminal, must be the last item in that response, and the response itself must be incomplete. After completed, the item no longer changes. On incomplete, arguments may be cut off; a partial JSON value should not run.
| status | Write function_call_output? |
|---|---|
in_progress | No. Wait until the stream finishes. |
incomplete | No. Terminal, and the arguments may be truncated. |
completed | Yes. Then confirm type has no colon, arguments passes the schema, and call_id matches the next item. |
| Any other value, including failed | No. Do not run a side effect. |
The gate below is the same code that checks this article's samples. It returns a reason in order: colon, not a function_call, status, arguments type, parse, then schema. Only ok may run the tool and append function_call_output.
def actual_type(data: object) -> str:
if data is None:
return "null"
if isinstance(data, bool):
return "boolean"
if isinstance(data, list):
return "array"
if isinstance(data, int):
return "integer"
if isinstance(data, float):
return "number"
if isinstance(data, str):
return "string"
if isinstance(data, dict):
return "object"
raise TypeError(type(data))
def validate(data: object, schema: dict) -> list[str]:
errors: list[str] = []
if not isinstance(schema, dict):
return errors
types = schema.get("type")
if isinstance(types, str):
types = [types]
actual = actual_type(data)
if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
errors.append("type")
if "enum" in schema and not any(value == data for value in schema["enum"]):
errors.append("enum")
if isinstance(data, dict) and schema.get("properties"):
for key in schema.get("required") or []:
if key not in data:
errors.append(f"required:{key}")
if schema.get("additionalProperties") is False:
for key in data:
if key not in schema["properties"]:
errors.append(f"additional:{key}")
for key, sub in schema["properties"].items():
if key in data:
errors.extend(validate(data[key], sub))
if isinstance(data, list) and schema.get("items"):
for item in data:
errors.extend(validate(item, schema["items"]))
return errors
def replay_gate(item: dict, schema: dict) -> str:
item_type = item.get("type")
if not isinstance(item_type, str) or ":" in item_type:
return "namespaced"
if item_type != "function_call":
return "not_function_call"
if item.get("status") != "completed":
return "status"
arguments = item.get("arguments")
if not isinstance(arguments, str):
return "arguments_type"
try:
parsed = json.loads(arguments)
except json.JSONDecodeError:
return "arguments_parse"
if validate(parsed, schema):
return "arguments_schema"
return "ok"
How do you inspect the three fields in JSONNote?
- 1See whether arguments is a string
Open JSON Format. Paste the whole item. If arguments expands into an object instead of one string, the stored shape is already wrong and the gate returns arguments_type.
- 2Then validate the parsed parameters
Paste the object parsed from the arguments string into JSON Schema. Use the region enum above as the schema. global is flagged; do not rewrite it to apac before the tool runs.
- 3Then diff the two providers' items
Open JSON Diff. Put the original provider item on the left and the item you plan to replay on the right. If type changes from function_call to a colon-prefixed extension, or call_id does not match, do not send the next request.
Common questions
Once Open Responses is in the AAIF, can the same JSON go straight to Bedrock or another implementation?
No. The portable core is message, function_call, and function_call_output. Do not assume a provider-prefixed item replays on another server. Claude structured outputs on Bedrock constrain a different layer: JSON Schema for model text, not this item envelope.
Why is arguments not stored as an object?
The spec examples store arguments as a JSON string. Streaming appends it with function_call_arguments.delta, and until status is completed that string may still be incomplete. Do not parse early and run the tool.
If status is incomplete, can I run the tool anyway?
No. incomplete is terminal. The spec requires it to be the last item of that response, and the response itself must be incomplete. A partial arguments value usually fails the schema; even if it happens to pass, do not treat it as a finished call.
If allowed_tools already limits tool names, do I still need a local schema?
Yes. allowed_tools limits which tool names may actually run on this request; the full tool list can still sit in tools. It does not check whether region inside arguments is in the enum. An allowed name still needs parse plus schema.
Can Chat Completions tool_calls sit in the same array as Open Responses items?
No. Open Responses uses type, call_id, and a string arguments. Chat Completions uses a tool_calls array. Mixed into one log, a replay sends the wrong envelope to the next provider. Diff the two documents separately.
Summary
After Open Responses joined the AAIF, providers can share the item shape. They do not share every log line.
Decision: a colon in type means it is not a portable function_call; confirm arguments is a string, then validate it with the tool schema; if status is not completed, do not write function_call_output.
Next: format the item to see if arguments is a string, validate the parsed parameters with Schema, and diff the two providers' items.