Repository navigation
Comparing changes
Open a pull request
base repository: a2aproject/a2a-python
base: v1.1.5
head repository: a2aproject/a2a-python
compare: v1.2.0
- 12 commits
- 60 files changed
- 7 contributors
Commits on Sep 21, 2026
-
ci: auto-assign triage owner on new issues (#1264)
Assigns a triage owner automatically when an issue is opened, so new reports surface immediately instead of waiting to be noticed. __Tested:__ Verified end to end on a fork: the run triggers, the assignee is set, and the issue notification is delivered.
Configuration menu - View commit details
-
Copy full SHA for a4c68f6 - Browse repository at this point
Copy the full SHA a4c68f6View commit details -
docs(samples): add Agent Card signing sample (#1198)
There was no runnable example for `a2a.utils.signing`, so this adds one covering both halves of Agent Card signing. `samples/agent_card_signing.py`: - **Server**: generates an ES256 key, signs the Agent Card with `create_agent_card_signer`, serves it via `create_agent_card_routes(card_modifier=...)`, and publishes the public key as a JWKS document at `/.well-known/jwks.json` — the URL the signature's `jku` points to. - **Client**: verifies the card with `create_signature_verifier` passed to `A2ACardResolver.get_agent_card(signature_verifier=...)`, resolving keys by `kid` from an allowlist of pinned JWKS URLs, with algorithms pinned to `ES256`. - **Modes**: `demo` (default, runs both in-process), `serve`, `verify --url`. The default `demo` mode verifies the genuine card and then shows the three rejections a verifier exists for: ``` 1. The card served by the agent verified card for agent: Signed Card Agent protected header: {'alg': 'ES256', 'jku': '.../.well-known/jwks.json', 'kid': 'sample-card-key-1', 'typ': 'JOSE'} 2. A card whose transport URL was rewritten in transit rejected as expected: No valid signature found 3. A card with its signature stripped off rejected as expected: AgentCard has no signatures to verify. 4. The genuine card, checked by a client that pins other keys rejected as expected: No valid signature found ``` Two non-obvious details are called out in comments, since they bite anyone copying the pattern: - The verifier is a **synchronous** callable, so fetching the JWKS inside it with a sync HTTP client blocks the event loop (it deadlocks against an in-process server and reports `No valid signature found`). The key provider does pure in-memory lookups over JWKS prefetched asynchronously. - `create_agent_card_signer` **mutates** the card it signs, so a per-request `card_modifier` would accumulate signatures. The helper deep-copies first and the card is signed once at startup. The key provider also raises `PyJWKError` rather than a plain exception, so the verifier treats one unusable signature as unverifiable and moves to the next instead of aborting a card that also carries a good signature. Also adds `tests/integration/test_agent_card_signing_smoke.py` (runs the demo on a free port, asserts one acceptance and three rejections) and a `samples/README.md` section. No library code changed — samples, tests and docs only. ## Testing - `uv run pytest tests/utils/test_signing.py tests/integration/test_agent_card_signing_smoke.py tests/client/test_card_resolver.py` → 55 passed - `uv run ruff format --check` and `uv run ruff check` → clean - `uv run ty check` → same 4 pre-existing diagnostics as `main` (samples are outside `[tool.ty.src]`) ## Checklist - [x] Follow the [`CONTRIBUTING` Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - [x] Ensure the tests and linter pass (Run `bash scripts/format.sh` from the repository root to format) - [x] Appropriate docs were updated (if necessary) Fixes #1197 🦕 --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: mykytanetipa <mykytanetipa@google.com>Configuration menu - View commit details
-
Copy full SHA for f3ac824 - Browse repository at this point
Copy the full SHA f3ac824View commit details
Commits on Sep 24, 2026
-
fix: compare in-memory task timestamps numerically (#1233)
# Description Fix timestamp filtering and descending ordering in `InMemoryTaskStore.list()`. Comparing `ToJsonString()` values lexicographically reverses chronological order when fractional-second precision differs. For example, `2025-01-01T00:00:00.123Z` incorrectly passes a filter whose threshold is `2025-01-01T00:00:00.123000001Z`, despite being one nanosecond earlier. The same comparison also produces incorrectly ordered pages. Compare integer nanoseconds using `ToNanoseconds()` for both filtering and sorting. Preserve the inclusive filter boundary, descending ID tie-breaker, and placement of tasks without timestamps after timestamped tasks. [A2A specification §5.6.1, Timestamps](https://a2a-protocol.org/latest/specification/#561-timestamps) states: “Millisecond precision SHOULD be used where available”. It also specifies `google.protobuf.Timestamp`, whose representation contains seconds and nanoseconds, and permits omitted fractional seconds. Numeric comparison handles these precisions without discarding sub-millisecond information. Add 15 regression cases covering seconds, milliseconds, microseconds, nanoseconds, equal timestamps, second boundaries, pagination, ID tie-breaking, absent timestamps/status, and timestamps before/at the Unix epoch. Before the fix, 11 cases fail and four pass; after the fix, all 35 in-memory task store tests pass. Validation on Python 3.10 with PostgreSQL 15 and MySQL 8: - `./scripts/lint.sh`: Ruff check/format and ty passed (three existing unused-ignore warnings outside this change). - `uv run pytest`: 2,092 passed, 3 xfailed, 1 xpassed. The xfail/xpass cases are already marked for #869. - `uv run pytest --cov=src --cov-report=term-missing --cov-fail-under=88`: same test results, 93.17% coverage. - JSCPD: 2.61% duplication, below the 3% threshold. - Built the wheel and ran `tests.install_smoke` in six clean environments: base, http-server, fastapi, grpc, telemetry, and sql all passed. Local transport checks used proxy exclusions for loopback addresses. Cross-version `uv --with` environments were prepared before the full runs to keep dependency downloads outside server-startup timeouts. - [x] Follow the CONTRIBUTING guide: feature branch based on current upstream main. - [x] Conventional Commit title: `fix: compare in-memory task timestamps numerically`. - [x] Tests and linters pass. - [x] Protocol reference included above; no public API changes. Co-authored-by: Gatsuk Andrey <GatsukAS@zdrav.mos.ru> Co-authored-by: mykytanetipa <mykytanetipa@google.com>
Configuration menu - View commit details
-
Copy full SHA for 0d5473c - Browse repository at this point
Copy the full SHA 0d5473cView commit details
Commits on Sep 29, 2026
-
fix(server): reject terminal-task operations with UnsupportedOperatio…
…nError (#1268) ## What Spec [3.1.1](https://a2a-protocol.org/latest/specification/#311-send-message) names `UnsupportedOperationError` for "Messages sent to Tasks that are in a terminal state", and 3.3.2 repeats it for any operation on a terminal task. 5.4 maps that to `-32004` / `FAILED_PRECONDITION` / HTTP 400. `ActiveTask` raised `InvalidParamsError` (`-32602`) instead, at all three of its terminal-state guards. That covers both `send_message` and `subscribe_to_task`, because `DefaultRequestHandlerV2.on_subscribe_to_task` has no terminal check of its own: it calls `get_or_create`, which calls `ActiveTask.start()`, and the error surfaces out of the first `__anext__`. Worth knowing while reading: `DefaultRequestHandler` is an alias for `DefaultRequestHandlerV2` (`request_handlers/__init__.py:46`), so `default_request_handler.py` is the legacy handler and is not on the default path. It already answered subscribe correctly at line 613, which is why only the v2 path drifted. Its send path is corrected here too, so the two handlers and the two operations finally agree. ## Cancel keeps its own error `on_cancel_task` deliberately catches `ActiveTask`'s terminal-state error and re-raises `TaskNotCancelableError`, which 3.3.2 gives its own entry. That remap is retained, now keyed on the new error type. Two ACTS tests (`CORE-CANCEL-002`, `CORE-ERR-002`) cover it and still pass. ## Tests `test_on_subscribe_to_task_in_terminal_state` is new for v2. Its absence is why this survived: the legacy suite had the equivalent test and asserted the correct error all along, so the divergence never showed up. Mutation-checked - reverting `active_task.py:472` alone fails all four parametrized cases. An existing `# TODO: Is it correct error code ?` in `test_scenarios.py` sat next to this exact assertion. It is now answered rather than carried forward. ## Result Clears `CORE-SEND-002`, `CORE-ERR-004` and `STREAM-SUB-003` (all `must`) on jsonrpc, grpc and rest - 9 of the 21 failing MUST test-instances. | binding | must before | must after | | --- | --- | --- | | jsonrpc | 52/59 | 55/59 | | grpc | 40/47 | 43/47 | | rest | 42/49 | 45/49 |
Configuration menu - View commit details
-
Copy full SHA for 6cce91b - Browse repository at this point
Copy the full SHA 6cce91bView commit details -
feat(server): add opt-in validation of message media types against th…
…e agent card (#1269) ## What Spec [3.3.2](https://a2a-protocol.org/latest/specification/#332-error-types) defines `ContentTypeNotSupportedError` for "A Media Type provided in the request's message parts ... is not supported by the agent". `ContentTypeNotSupportedError` was fully wired already - `-32005`, `INVALID_ARGUMENT`, HTTP 400, gRPC `INVALID_ARGUMENT` - but nothing ever read `AgentCard.default_input_modes`, so an undeclared `mediaType` was always accepted. This is the same change a2a-js made in [a2aproject/a2a-js#745](a2aproject/a2a-js#745), and it lands in the same place: a shared helper called from the one setup path that both `send_message` and `send_message_stream` go through, in each handler. ## Why opt-in Always-on would start refusing traffic these agents accept today. Four cards in this repo declare input modes their clients contradict: | card | declares | | --- | --- | | `samples/hello_world_agent.py:156` | `['text']` | | `samples/agent_card_signing.py:146` | `['text']` | | `itk/main.py:720` | `['text/plain']` | | `itk/acts_client_parse.py:123` | `['text/plain']` | `'text'` is not a media type, so a client sending `text/plain` against it would be rejected. a2a-js hit exactly this and reached the same conclusion. The ITK fixture opts in, since its card states its modes accurately. ## Two carve-outs A card declaring **no** input modes accepts everything - an empty `default_input_modes` is an absent declaration, not an empty allowlist. A part with **no** `media_type` is not checked. It is a proto3 string, so an absent one arrives as `''`; only a media type the client actually stated can contradict the card. ## Tests Six unit tests on the validator, three on the handler wiring, two on the legacy handler. The handler tests matter more than they look: a flag that is stored but never read is the failure mode here, so both states are asserted and both were mutation-checked - forcing the branch off fails the two rejection tests, forcing it on fails the opt-out test. ## Result Clears `CORE-SEND-004` (`must`) on all three bindings.
Configuration menu - View commit details
-
Copy full SHA for 8037b24 - Browse repository at this point
Copy the full SHA 8037b24View commit details -
fix(server): reject a message whose contextId disagrees with its task (…
…#1270) ## What Spec [3.4.3](https://a2a-protocol.org/latest/specification/#343-multi-turn-conversation-patterns): "Agents **MUST** reject messages containing mismatching `contextId` and `taskId` (i.e., the provided `contextId` is different from that of the referenced `Task`)." A follow-up carrying a real `taskId` with someone else's `contextId` was accepted with HTTP 200, and the client's `contextId` was then written into the task's history while the `Task` kept its own - two disagreeing context ids inside one task. ## This guard already existed `RequestContext` has carried it all along (`agent_execution/context.py:77-78`). It only fires when a non-`None` `task` reaches `build()`, and v2 passes `task=None` on purpose: ```python # We will get the task when we have to process the request to avoid concurrent read/write issues. task=None, ``` So the check went dead when V2 became the default. The legacy handler passes the task and still rejects. Rather than undo that deliberate `task=None`, the comparison goes where v2 already fetches the task to prove it exists, one line after the `TaskNotFoundError` it raises there. No extra read, and the task is not retained. ## Scoped to requests that name a task The check only runs under `if original_task_id:`. A `contextId` with no `taskId` is a new task in an existing context, which stays legal - `test_scenario_context_id_visibility` depends on it, and there is a test here pinning it. ## A test that passed for the wrong reason Worth flagging for review. `TaskManager` already rejects this mismatch *once the agent emits an event*, with `Context in event doesn't match TaskManager wrong-context : real-context`. That message happens to contain both context ids, so the obvious assertion - `pytest.raises(InvalidParamsError)` plus both ids in the message - passes with or without this change. It did, and mutation testing is what caught it. The test now asserts the executor was never awaited. That is the actual behavioural difference: the request is refused before any agent work starts, instead of after the agent has already been handed a request whose ids contradict each other. ## Result Clears `CORE-MULTI-006` (`must`) on all three bindings.
Configuration menu - View commit details
-
Copy full SHA for c25022f - Browse repository at this point
Copy the full SHA c25022fView commit details -
fix(server): send PushNotificationConfig.authentication as an Authori…
…zation header (#1271) ## What Spec [4.3.3](https://a2a-protocol.org/latest/specification/#433-push-notification-payload) gives the webhook request an `Authorization: {authentication_scheme} {credentials}` header, built from `PushNotificationConfig.authentication`. `BasePushNotificationSender` never read that field - the only header it has ever sent is `X-A2A-Notification-Token`, from the sibling `token`. A receiver therefore had no credential to check and no way to tell a genuine push from a forged one, which is the whole point of registering `authentication` in the first place. Nothing suggests this was left to the application. The constructor docstring explicitly justifies delegating URL screening, on the grounds that the spec makes it a SHOULD, and says nothing about auth, which the spec makes a MUST. The code already reads the field next to it. This reads as an omission. ## Only when both halves are present `AuthenticationInfo` is a message, so a config can set `scheme` without `credentials`. Sending on that would put `Bearer ` with nothing after it on the wire - a header that looks like a credential and is not. Both must be non-empty. Note the singular `scheme`. Only the v0.3 compat type has repeated `schemes`, and `conversions.py:252-261` collapses it to `schemes[0]` on the way in, so the sender only ever deals with one. ## Heads-up on the ACTS side `SEC-PUSH-001`, `SEC-PUSH-002` and `PUSH-DELIV-001` currently pass in nightly CI for every SDK, so this may look like a fix to something that was not broken. It is not: the assertion that inspects the delivered request is new, added in [a2aproject/A2A@82c277f](a2aproject/A2A@82c277f). The corpus CI runs predates it, and the older tests only checked that the config API accepted the config - which, as that commit's own note puts it, "a server that stores it and pushes anonymously, or never pushes, satisfies". A consequence worth stating: no other SDK has been measured against the real assertion yet, so unlike the rest of this stack there is no cross-SDK corroboration here. The code is the evidence - `push_info.authentication` appears nowhere under `src/a2a/server/` before this change. ## Result Clears `SEC-PUSH-001`, `SEC-PUSH-002` (`must`) and `PUSH-DELIV-001` (`may`) on all three bindings, and with them the **last failing MUST**: | binding | must | verdict | | --- | --- | --- | | jsonrpc | 59/59 | CONFORMANT | | grpc | 47/47 | CONFORMANT | | rest | 49/49 | CONFORMANT | The three PRs after this one are SHOULD-level and change the verdict for nobody; they take the remaining non-MUST failures to zero. Closes #585
Configuration menu - View commit details
-
Copy full SHA for 5751d31 - Browse repository at this point
Copy the full SHA 5751d31View commit details -
feat(server): add caching headers to the agent card endpoint (#1272)
## What Spec [8.6.1](https://a2a-protocol.org/latest/specification/#861-server-requirements) asks the card endpoint for a `Cache-Control` header with a `max-age`, and an `ETag` "derived from the Agent Card's `version` field or a hash of the card content". It sent neither, so every client refetched the whole card every time with no way to revalidate - for content the spec itself describes as changing "infrequently relative to the frequency at which clients may fetch it". One serving site, `agent_card_routes.py`. FastAPI re-wraps the same endpoint rather than building its own response, so there is no second variant to keep in step. ## Hash rather than version 8.6.1 allows either, but `card_modifier` can return a different card per request without touching `version`, and a validator that goes stale silently is worse than none. Hashing the card actually being served stays correct under it. There is a test for exactly that. `json.dumps(sort_keys=True)` rather than the RFC 8785 canonicalization this repo already has for signing: an ETag is opaque and is only ever compared against one this server produced, so determinism is the entire requirement, and JCS would add a depth limit and a failure mode for nothing. The signing canonicalizer also strips `signatures`, which would be wrong here - a card whose signatures changed is a different response body. ## Conditional requests `If-None-Match` is honoured with a 304. Emitting a validator and then ignoring the header that uses it would leave clients re-downloading the card anyway, which is the thing 8.6 exists to avoid. Handled per RFC 9110 8.2.2: comparison is weak, so `W/"x"` matches `"x"`, `*` matches, and a list is accepted. ## Cache-Control is opt-in 8.6.1 ties `max-age` to "the agent's expected update frequency", which the SDK cannot know - a card regenerated per deployment and one static for months want very different numbers, and a wrong guess baked into the library is a stale card nobody can explain. It is a parameter with no default. The `ETag` ships either way, so the endpoint satisfies 8.6.1's either/or unconfigured, and revalidation costs one 304. ## Result Clears `CARD-CACHE-001` (`should`) on all three bindings, and takes grpc to zero failures at every level.
Configuration menu - View commit details
-
Copy full SHA for 83d7f5d - Browse repository at this point
Copy the full SHA 83d7f5dView commit details -
fix(server): ignore unrecognized request fields and fix parse-error d…
…ata shape (#1273) Two problems on the same code path. ## Unrecognized fields Spec [5.7](https://a2a-protocol.org/latest/specification/#57-field-presence-and-optionality): "Implementations **SHOULD** ignore unrecognized fields in messages, allowing for forward compatibility as the protocol evolves." Both dispatchers parsed strictly, so `ignore_unknown_fields` defaulted to `False` and a client one minor version ahead was rejected outright over a field this build has never heard of. Four call sites, one flag. The v0.3 compat handler was already lenient, so this brings 1.0 into line with it. ## The flag also covers enum values `ignore_unknown_fields` is not only about field names: protobuf applies the same rule one level down and accepts an unknown enum value, defaulting it to 0. A future `ROLE_SYSTEM` now arrives as `ROLE_UNSPECIFIED` instead of failing the request. That is the same forward-compatibility bargain and arguably what 5.7 is for, but it is a real widening and protobuf offers no way to take the field leniency without it. It is commented at the call site and pinned by a test, so it is a decision on the record rather than something to be rediscovered later. **Reviewers: this is the part worth a second opinion.** ## error.data was a bare string Spec [9.5](https://a2a-protocol.org/latest/specification/#95-json-rpc-error-handling) makes `error.data` an "array of objects, each containing a `@type` key", and every object in it "**MUST** include a `@type` key". Only the `A2AError` path runs `build_error_details`, which produces that array. The parse-failure branch constructed the pydantic JSON-RPC model instead, whose `data` is `Any`, so the raw ProtoJSON diagnostic went out as a bare string. Both parse-failure sites now raise the `A2AError`, and the diagnostic survives in `ErrorInfo.metadata` rather than being dropped. No ACTS test reaches this - the corpus cannot construct a request that fails ProtoJSON parsing and then inspect `data` - so it is a genuine MUST violation that conformance would not have caught either way. ## Three tests moved off payloads that leniency now accepts `{'message': 'should be an object'}` and `{'message': {'text': 'Hello'}}` both parse to an empty `Message` now, so they no longer stop at the parser. All three tests use mock handlers, which is why they saw it: they pin the parser layer with nothing behind it. Against a **real** handler, both payloads still answer `-32602`, from required-field validation one layer down. Verified before changing them: ``` message as string -> error -32602 Validation failed unknown key only -> error -32602 Validation failed valid + unknown -> OK ``` So the end-to-end contract is unchanged; what moved is which layer rejects. The tests now use a value of the wrong shape (`parts` as a string), which leniency does not excuse, and each carries a comment saying why. ## Result Clears `DM-EXTRA-001` (`should`, jsonrpc only), taking jsonrpc to zero failures at every level.
Configuration menu - View commit details
-
Copy full SHA for 83f1cf8 - Browse repository at this point
Copy the full SHA 83f1cf8View commit details -
feat(rest): serve HTTP+JSON responses as application/a2a+json (#1274)
## What Spec [11.1](https://a2a-protocol.org/latest/specification/#111-protocol-requirements): "**Content-Type:** application/a2a+json **SHOULD** be used for requests and responses". The spec uses it in every worked HTTP+JSON example - 20-plus of them. Every REST response inherited Starlette's `JSONResponse.media_type` default of `application/json`. `application/a2a+json` appeared nowhere in the repo, so the constant is new. ## Worth knowing: nobody implements this All four SDKs with published ACTS metrics - python, go, java, rust - fail `REST-CT-001`. It is a SHOULD, so it blocks no one's conformance, and a reasonable response to this PR is "not yet, let the others move first". Merging it makes a2a-python the first. Flagging it rather than burying it, since the other six PRs in this stack all had at least one SDK already passing to corroborate them. ## Two limits, both deliberate **Success responses only.** The error path runs through `rest_error_handler`, which `compat/v0_3/rest_adapter.py` shares. 11.1 is the 1.0 binding's requirement, and v0.3 predates the media type, so changing that decorator would alter a different protocol version's wire format. Splitting it would mean making a bare decorator parameterized and touching every call site in both bindings - not worth it for a SHOULD the corpus does not test on the error path. The result is that 1.0 errors still answer `application/json`; worth a follow-up, not worth bundling here. **Responses only, not requests.** 11.1 says both, but changing what the client *sends* risks servers that only accept `application/json`, and no test covers it. **No `JSONResponse` subclass.** It would have reduced nine sites to an import swap, but `rest_dispatcher.py` aliases `JSONResponse = Any` when Starlette is absent, and `class X(Any)` is a `TypeError` on Python 3.10, which this package supports. Passing `media_type=` explicitly keeps the no-Starlette path importable and the sites greppable. ## Result Clears `REST-CT-001` (`should`, rest only). With it, **all three bindings are at zero failures at every level**: | binding | passed | must | failed | | --- | --- | --- | --- | | jsonrpc | 101/111 | 59/59 | 0 | | grpc | 88/111 | 47/47 | 0 | | rest | 92/111 | 49/49 | 0 | The remainder are skips whose preconditions the fixture does not meet, not failures.
Configuration menu - View commit details
-
Copy full SHA for dc5a5da - Browse repository at this point
Copy the full SHA dc5a5daView commit details -
feat(server): add multi-replica cluster mode (#1281)
Adds a2a.server.cluster, key interfaces (__VersionedTaskStore__ and __TaskEventStream__) for running the A2A server as several replicas, with no need to route a task's requests back to the replica that started it, and wires DefaultRequestHandlerV2 to use them so any replica can serve any request for a remote task. __Opt-in and backward compatible:__ with no event stream and a plain (non-versioned) TaskStore, pre-existing behavior is preserved. __Key changes:__ - VersionedTaskStore - a task store that keeps track of which revision of a task a replica last read. When that replica saves, the store checks the task has not changed in the meantime and refuses the write / retries if it has, instead of quietly overwriting whatever another replica just did. VersionedDatabaseTaskStore is the database-backed implementation. - TaskEventStream - a shared record of the events a task produces, so a replica that is not running the agent can still see the event stream. This lets a client reconnect to any replica and keep receiving updates for a task running elsewhere. DatabaseTaskEventStream is the database-backed implementation. - Schema for both: a new task_versions table and a new task_events table. The tasks table is unchanged. - New optional event_stream argument; task_store now accepts a plain or a versioned store and is still exposed unchanged as self.task_store. - Cancel works from any replica. The cancelling replica records the cancellation in the shared store, and the replica actually running the agent notices on its next save and stops. - Resubscribe works from any replica. It returns the current task, then follows the shared event stream from that point on, so the client sees no gap and no duplicates. - Alembic revision b5e3d1c8a2f7 (revises 38ce57e08137) creates the task_versions table and the task_events table with an index on task_id. It only creates tables and does not alter existing ones. - A runnable sample showing how to deploy the server as several replicas: it starts N replicas as separate processes sharing one database, and a driver starts a task on one replica, watches it from a second, and cancels it from a third. Defaults to a shared SQLite file so it runs with no external services; set A2A_CLUSTER_DSN to point every replica at Postgres or MySQL instead. __Tested:__ - Integration tests run two DefaultRequestHandlerV2 replicas against one real Postgres or MySQL database, each replica on its own engine. They cover send, resubscribe and cancel handled by a replica other than the one running the agent, and a multi-turn input_required conversation that moves between replicas. - An integration test sends follow-ups to the same task from both replicas at once and asserts that neither turn is lost. - Unit tests cover the same cross-replica flows without a database, using in-memory versions of the shared store and event stream. - Unit tests for the migration's full up/down cycle, idempotency and offline SQL generation. - Adds the database-backed cluster tests to scripts/run_db_tests.sh. Run the sample from the repository root: ``` python -m samples.clustermode.run_cluster --replicas 3 --base-port 41241 python -m samples.clustermode.exercise --host 127.0.0.1 --ports 41241,41242,41243 ``` __Design discussion:__ #1224 Release-As: 1.2.0
Configuration menu - View commit details
-
Copy full SHA for 942d621 - Browse repository at this point
Copy the full SHA 942d621View commit details -
chore(main): release 1.2.0 (#1266)
🤖 I have created a release *beep* *boop* --- ## [1.2.0](v1.1.5...v1.2.0) (2026-09-29) ### Features * **rest:** serve HTTP+JSON responses as application/a2a+json ([#1274](#1274)) ([dc5a5da](dc5a5da)) * **server:** add caching headers to the agent card endpoint ([#1272](#1272)) ([83d7f5d](83d7f5d)) * **server:** add multi-replica cluster mode ([#1281](#1281)) ([942d621](942d621)) * **server:** add opt-in validation of message media types against the agent card ([#1269](#1269)) ([8037b24](8037b24)) ### Bug Fixes * compare in-memory task timestamps numerically ([#1233](#1233)) ([0d5473c](0d5473c)) * **server:** ignore unrecognized request fields and fix parse-error data shape ([#1273](#1273)) ([83f1cf8](83f1cf8)) * **server:** reject a message whose contextId disagrees with its task ([#1270](#1270)) ([c25022f](c25022f)) * **server:** reject terminal-task operations with UnsupportedOperationError ([#1268](#1268)) ([6cce91b](6cce91b)) * **server:** send PushNotificationConfig.authentication as an Authorization header ([#1271](#1271)) ([5751d31](5751d31)), closes [#585](#585) ### Documentation * **samples:** add Agent Card signing sample ([#1198](#1198)) ([f3ac824](f3ac824)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Configuration menu - View commit details
-
Copy full SHA for 5ca9859 - Browse repository at this point
Copy the full SHA 5ca9859View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v1.1.5...v1.2.0