diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..bb3b3574
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,7 @@
+# Code owners file.
+# This file controls who is tagged for review for any given pull request.
+#
+# For syntax help see:
+# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
+
+* @google/a2a-eng
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 00000000..9d0b58a4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,33 @@
+name: ๐ Bug Report
+description: File a bug report
+title: "[Bug]: "
+type: "Bug"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for stopping by to let us know something could be better!
+ Private Feedback? Please use this [Google form](https://goo.gle/a2a-feedback)
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Also tell us what you expected to happen and how to reproduce the issue.
+ placeholder: Tell us what you see!
+ value: "A bug happened!"
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/google/A2A?tab=coc-ov-file#readme)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 00000000..c470a2de
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,41 @@
+name: ๐ก Feature Request
+description: Suggest an idea for this repository
+title: "[Feat]: "
+type: "Feature"
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for stopping by to let us know something could be better!
+ Private Feedback? Please use this [Google form](https://goo.gle/a2a-feedback)
+ - type: textarea
+ id: problem
+ attributes:
+ label: Is your feature request related to a problem? Please describe.
+ description: A clear and concise description of what the problem is.
+ placeholder: Ex. I'm always frustrated when [...]
+ - type: textarea
+ id: describe
+ attributes:
+ label: Describe the solution you'd like
+ description: A clear and concise description of what you want to happen.
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Describe alternatives you've considered
+ description: A clear and concise description of any alternative solutions or features you've considered.
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional context
+ description: Add any other context or screenshots about the feature request here.
+ - type: checkboxes
+ id: terms
+ attributes:
+ label: Code of Conduct
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/google/a2a-python?tab=coc-ov-file#readme)
+ options:
+ - label: I agree to follow this project's Code of Conduct
+ required: true
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..24798217
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+# Description
+
+Thank you for opening a Pull Request!
+Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
+
+- [ ] Follow the [`CONTRIBUTING` Guide](https://github.com/google/a2a-python/blob/main/CONTRIBUTING.md).
+- [ ] Make your Pull Request title in the specification.
+- [ ] Ensure the tests and linter pass (Run `nox -s format` from the repository root to format)
+- [ ] Appropriate docs were updated (if necessary)
+
+Fixes # ๐ฆ
diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md
new file mode 100644
index 00000000..2c0631a0
--- /dev/null
+++ b/.github/actions/spelling/advice.md
@@ -0,0 +1,28 @@
+
+If the flagged items are :exploding_head: false positives
+
+If items relate to a ...
+
+- binary file (or some other file you wouldn't want to check at all).
+
+ Please add a file path to the `excludes.txt` file matching the containing file.
+
+ File paths are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
+
+ `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude `README.md` (on whichever branch you're using).
+
+- well-formed pattern.
+
+ If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
+ try adding it to the `patterns.txt` file.
+
+ Patterns are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
+
+ Note that patterns can't match multiline strings.
+
+
+
+
+
+:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
+please merge to your PR's base branch to get the version configured for your repository.
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
new file mode 100644
index 00000000..0d6f82d4
--- /dev/null
+++ b/.github/actions/spelling/excludes.txt
@@ -0,0 +1,87 @@
+# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
+(?:^|/)(?i)COPYRIGHT
+(?:^|/)(?i)LICEN[CS]E
+(?:^|/)(?i)CODE_OF_CONDUCT.md\E$
+(?:^|/)(?i).gitignore\E$
+(?:^|/)3rdparty/
+(?:^|/)go\.sum$
+(?:^|/)package(?:-lock|)\.json$
+(?:^|/)Pipfile$
+(?:^|/)pyproject.toml
+(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
+(?:^|/)vendor/
+/CODEOWNERS$
+\.a$
+\.ai$
+\.all-contributorsrc$
+\.avi$
+\.bmp$
+\.bz2$
+\.cer$
+\.class$
+\.coveragerc$
+\.crl$
+\.crt$
+\.csr$
+\.dll$
+\.docx?$
+\.drawio$
+\.DS_Store$
+\.eot$
+\.eps$
+\.exe$
+\.gif$
+\.git-blame-ignore-revs$
+\.gitattributes$
+\.gitkeep$
+\.graffle$
+\.gz$
+\.icns$
+\.ico$
+\.jar$
+\.jks$
+\.jpe?g$
+\.key$
+\.lib$
+\.lock$
+\.map$
+\.min\..
+\.mo$
+\.mod$
+\.mp[34]$
+\.o$
+\.ocf$
+\.otf$
+\.p12$
+\.parquet$
+\.pdf$
+\.pem$
+\.pfx$
+\.png$
+\.psd$
+\.pyc$
+\.pylintrc$
+\.qm$
+\.s$
+\.sig$
+\.so$
+\.svgz?$
+\.sys$
+\.tar$
+\.tgz$
+\.tiff?$
+\.ttf$
+\.wav$
+\.webm$
+\.webp$
+\.woff2?$
+\.xcf$
+\.xlsx?$
+\.xpm$
+\.xz$
+\.zip$
+^\.github/actions/spelling/
+^\Q.github/workflows/spelling.yaml\E$
+^\Q.github/workflows/linter.yaml\E$
+\.gitignore\E$
+\.vscode/
diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns
new file mode 100644
index 00000000..f87ad0b7
--- /dev/null
+++ b/.github/actions/spelling/line_forbidden.patterns
@@ -0,0 +1,307 @@
+# Should be `HH:MM:SS`
+\bHH:SS:MM\b
+
+# Should probably be `YYYYMMDD`
+\b[Yy]{4}[Dd]{2}[Mm]{2}(?!.*[Yy]{4}[Dd]{2}[Mm]{2}).*$
+
+# Should be `anymore`
+\bany more[,.]
+
+# Should be `cannot` (or `can't`)
+# See https://www.grammarly.com/blog/cannot-or-can-not/
+# > Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`.
+# > `Can't` is a contraction of `cannot`, and it's best suited for informal writing.
+# > In formal writing and where contractions are frowned upon, use `cannot`.
+# > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.`
+# - if you encounter such a case, add a pattern for that case to patterns.txt.
+\b[Cc]an not\b
+
+# Should be `GitHub`
+(?
+ Marking this issue as stale since it has been open for 14 days with no activity.
+ This issue will be closed if no further activity occurs.
+ close-issue-message: >
+ This issue was closed because it has been inactive for 27 days.
+ Please post a new issue if you need further assistance. Thanks!
+ days-before-pr-stale: 14
+ days-before-pr-close: 13
+ stale-pr-label: "status:stale"
+ stale-pr-message: >
+ Marking this pull request as stale since it has been open for 14 days with no activity.
+ This PR will be closed if no further activity occurs.
+ close-pr-message: >
+ This pull request was closed because it has been inactive for 27 days.
+ Please open a new pull request if you need further assistance. Thanks!
+ # Label that can be assigned to issues to exclude them from being marked as stale
+ exempt-issue-labels: "override-stale"
+ # Label that can be assigned to PRs to exclude them from being marked as stale
+ exempt-pr-labels: "override-stale"
diff --git a/.ruff.toml b/.ruff.toml
index b509651a..f4baf437 100644
--- a/.ruff.toml
+++ b/.ruff.toml
@@ -5,18 +5,6 @@
# This file follows the standards in Google Python Style Guide
# https://google.github.io/styleguide/pyguide.html
#
-# The settings below are for the IDE configuration, and are optional.
-#{
-# "editor.formatOnSave": true,
-# "[python]": {
-# "editor.defaultFormatter": "charliermarsh.ruff",
-# "editor.formatOnSave": true,
-# "editor.codeActionsOnSave": {
-# "source.organizeImports": "true"
-# },
-# },
-# "ruff.importStrategy": "fromEnvironment",
-#}
line-length = 80 # Google Style Guide ยง3.2: 80 columns
indent-width = 4 # Google Style Guide ยง3.4: 4 spaces
@@ -34,7 +22,7 @@ ignore = [
"ANN201",
"ANN204",
"D100", # Ignore Missing docstring in public module (often desired at top level __init__.py)
- "D102", # Ignore return type annotiation in public method
+ "D102", # Ignore return type annotation in public method
"D104", # Ignore Missing docstring in public package (often desired at top level __init__.py)
"D107", # Ignore Missing docstring in __init__ (use class docstring)
"TD002", # Ignore Missing author in TODOs (often not required)
@@ -137,4 +125,4 @@ inline-quotes = "single"
docstring-code-format = true
docstring-code-line-length = "dynamic" # Or set to 80
quote-style = "single"
-indent-style = "space"
\ No newline at end of file
+indent-style = "space"
diff --git a/SECURITY.md b/SECURITY.md
index 5d0d1a26..8b58ae9c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,4 +1,3 @@
-
# Security Policy
To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 00000000..4d8b3165
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,144 @@
+# pylint: skip-file
+# type: ignore
+# -*- coding: utf-8 -*-
+#
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import pathlib
+import subprocess
+
+import nox
+
+
+DEFAULT_PYTHON_VERSION = '3.13'
+
+CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
+
+nox.options.sessions = [
+ 'format',
+]
+
+# Error if a python version is missing
+nox.options.error_on_missing_interpreters = True
+
+
+@nox.session(python=DEFAULT_PYTHON_VERSION)
+def format(session):
+ """Format Python code using autoflake, pyupgrade, and ruff."""
+ # Sort Spelling Allowlist
+ spelling_allow_file = '.github/actions/spelling/allow.txt'
+
+ with open(spelling_allow_file, encoding='utf-8') as file:
+ unique_words = sorted(set(file))
+
+ with open(spelling_allow_file, 'w', encoding='utf-8') as file:
+ file.writelines(unique_words)
+
+ format_all = False
+
+ if format_all:
+ lint_paths_py = ['.']
+ else:
+ target_branch = 'origin/main'
+
+ unstaged_files = subprocess.run(
+ [
+ 'git',
+ 'diff',
+ '--name-only',
+ '--diff-filter=ACMRTUXB',
+ target_branch,
+ ],
+ stdout=subprocess.PIPE,
+ text=True,
+ check=False,
+ ).stdout.splitlines()
+
+ staged_files = subprocess.run(
+ [
+ 'git',
+ 'diff',
+ '--cached',
+ '--name-only',
+ '--diff-filter=ACMRTUXB',
+ target_branch,
+ ],
+ stdout=subprocess.PIPE,
+ text=True,
+ check=False,
+ ).stdout.splitlines()
+
+ committed_files = subprocess.run(
+ [
+ 'git',
+ 'diff',
+ 'HEAD',
+ target_branch,
+ '--name-only',
+ '--diff-filter=ACMRTUXB',
+ ],
+ stdout=subprocess.PIPE,
+ text=True,
+ check=False,
+ ).stdout.splitlines()
+
+ changed_files = sorted(
+ {
+ file
+ for file in (unstaged_files + staged_files + committed_files)
+ if os.path.isfile(file)
+ }
+ )
+
+ lint_paths_py = [f for f in changed_files if f.endswith('.py')]
+
+ if not lint_paths_py:
+ session.log('No changed Python files to lint.')
+ return
+
+ session.install(
+ 'types-requests',
+ 'pyupgrade',
+ 'autoflake',
+ 'ruff',
+ )
+
+ if lint_paths_py:
+ if not format_all:
+ session.run(
+ 'pyupgrade',
+ '--exit-zero-even-if-changed',
+ '--py311-plus',
+ *lint_paths_py,
+ )
+ session.run(
+ 'autoflake',
+ '-i',
+ '-r',
+ '--remove-all-unused-imports',
+ *lint_paths_py,
+ )
+ session.run(
+ 'ruff',
+ 'check',
+ '--fix-only',
+ *lint_paths_py,
+ )
+ session.run(
+ 'ruff',
+ 'format',
+ *lint_paths_py,
+ )