oo-a2a-registry 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # Agent settings
221
+ .claude
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: oo-a2a-registry
3
+ Version: 0.1.0
4
+ Summary: Server and client library for A2A agent discovery via heartbeat registration
5
+ Project-URL: Homepage, https://github.com/obics-eu/oo-a2a-registry
6
+ Project-URL: Issues, https://github.com/obics-eu/oo-a2a-registry/issues
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: a2a,agent,agent-to-agent,discovery,fastapi,registry
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: FastAPI
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: fastapi>=0.100.0
22
+ Requires-Dist: httpx>=0.24.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: httpx>=0.24.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: respx>=0.20; extra == 'dev'
29
+ Requires-Dist: uvicorn[standard]>=0.20.0; extra == 'dev'
30
+ Provides-Extra: server
31
+ Requires-Dist: uvicorn[standard]>=0.20.0; extra == 'server'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # oo-a2a-registry
35
+
36
+ Fast and lean AI agent registry implementing the [A2A protocol](https://a2a-protocol.org/) for agent discovery via heartbeat-based registration.
37
+
38
+ Supports **A2A v0.3** (top-level `url` field, `agent.json` discovery) and **A2A v1.0** (`supportedInterfaces`, `agent-card.json` discovery).
39
+
40
+ ## Overview
41
+
42
+ | Component | What it does |
43
+ |-----------|--------------|
44
+ | **Server** | Exposes `GET /.well-known/agents` returning live A2A agent cards. Accepts `POST /registry/heartbeat` from clients. Verifies agents by fetching their well-known card (`agent-card.json` or `agent.json`). Evicts agents that miss more than 3 consecutive heartbeats. |
45
+ | **Client** | Sends the agent's `AgentCard` to the registry on a configurable interval (default 60 s) as a background task. |
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install oo-a2a-registry # client + server (no ASGI server)
51
+ pip install "oo-a2a-registry[server]" # includes uvicorn
52
+ ```
53
+
54
+ ## Quick start
55
+
56
+ ### Standalone registry server
57
+
58
+ ```python
59
+ import uvicorn
60
+ from a2a_registry import AgentRegistryServer
61
+
62
+ server = AgentRegistryServer()
63
+ app = server.create_app()
64
+
65
+ if __name__ == "__main__":
66
+ uvicorn.run(app, host="0.0.0.0", port=8000)
67
+ ```
68
+
69
+ ### Mount into an existing FastAPI app
70
+
71
+ ```python
72
+ from fastapi import FastAPI
73
+ from a2a_registry import AgentRegistryServer
74
+
75
+ app = FastAPI()
76
+ registry = AgentRegistryServer()
77
+ registry.setup(app) # adds /.well-known/agents and /registry/heartbeat
78
+ ```
79
+
80
+ ### Agent client — A2A v0.3
81
+
82
+ ```python
83
+ import asyncio
84
+ from a2a_registry import AgentCard, RegistryClient
85
+
86
+ card = AgentCard(
87
+ name="My Agent",
88
+ url="http://my-agent:8080", # agent must serve /.well-known/agent.json
89
+ version="1.0.0",
90
+ )
91
+
92
+ async def main():
93
+ async with RegistryClient("http://registry:8000", card, interval=30) as client:
94
+ await asyncio.sleep(300) # heartbeats run in the background
95
+
96
+ asyncio.run(main())
97
+ ```
98
+
99
+ ### Agent client — A2A v1.0
100
+
101
+ ```python
102
+ import asyncio
103
+ from a2a_registry import AgentCard, AgentInterface, RegistryClient
104
+
105
+ card = AgentCard(
106
+ name="My Agent",
107
+ supportedInterfaces=[
108
+ AgentInterface(
109
+ url="http://my-agent:8080/jsonrpc",
110
+ protocolBinding="json-rpc/2.0",
111
+ protocolVersion="1.0",
112
+ )
113
+ ],
114
+ version="1.0.0",
115
+ )
116
+
117
+ async def main():
118
+ async with RegistryClient("http://registry:8000", card, interval=30) as client:
119
+ await asyncio.sleep(300)
120
+
121
+ asyncio.run(main())
122
+ ```
123
+
124
+ ### Send a single heartbeat
125
+
126
+ ```python
127
+ client = RegistryClient("http://registry:8000", card)
128
+ result = await client.send_once()
129
+ print(result.verified)
130
+ ```
131
+
132
+ ## Examples
133
+
134
+ Runnable examples are in the [`examples/`](examples/) directory:
135
+
136
+ | File | Description |
137
+ |------|-------------|
138
+ | [`registry_server.py`](examples/registry_server.py) | Standalone registry on port 8000 |
139
+ | [`hello_world_agent.py`](examples/hello_world_agent.py) | Hello World agent (A2A v0.3) on port 8001 |
140
+ | [`hello_world_agent_v1.py`](examples/hello_world_agent_v1.py) | Hello World agent (A2A v1.0) on port 8002 |
141
+
142
+ ```bash
143
+ pip install "oo-a2a-registry[server]"
144
+ python examples/registry_server.py &
145
+ python examples/hello_world_agent.py &
146
+ curl -s http://localhost:8000/.well-known/agents | python -m json.tool
147
+ ```
148
+
149
+ ## A2A version compatibility
150
+
151
+ The registry accepts agent cards from both protocol versions and auto-detects the discovery path.
152
+
153
+ | | A2A v0.3 | A2A v1.0 |
154
+ |---|---|---|
155
+ | Agent URL field | top-level `url` | `supportedInterfaces[].url` |
156
+ | Discovery path | `/.well-known/agent.json` | `/.well-known/agent-card.json` |
157
+ | Protocol version | `protocolVersion` (card-level) | `supportedInterfaces[].protocolVersion` |
158
+
159
+ Cards without a top-level `url` must provide `supportedInterfaces`. The registry uses `AgentCard.effective_url` as the canonical key (returns `url` for v0.3, `supportedInterfaces[0].url` for v1.0).
160
+
161
+ When verifying an agent the registry tries the preferred discovery path first (based on which fields the heartbeat card has) and falls back to the other automatically.
162
+
163
+ ## How it works
164
+
165
+ ```
166
+ Agent Registry Server
167
+ | |
168
+ |-- POST /registry/heartbeat ----> |
169
+ | { agent_card, interval } |
170
+ | |-- GET {origin}/.well-known/agent-card.json (v1.0)
171
+ | | — or —
172
+ | |-- GET {origin}/.well-known/agent.json (v0.3)
173
+ | | (once, to verify reachability)
174
+ | |-- store as "available"
175
+ |<-- { status: "ok", verified } -- |
176
+ | |
177
+ |-- POST /registry/heartbeat ----> | (every `interval` seconds)
178
+ | |-- refresh last_heartbeat timestamp
179
+ | |
180
+ | (silence > 3 × interval) |-- evict from registry
181
+ ```
182
+
183
+ `GET /.well-known/agents` returns only agents with `status == available`.
184
+
185
+ ## Custom storage backend
186
+
187
+ Implement `RegistryProvider` to plug in Redis, a database, etc.:
188
+
189
+ ```python
190
+ from a2a_registry.server import RegistryProvider
191
+
192
+ class RedisRegistryProvider(RegistryProvider):
193
+ async def upsert(self, agent_card, interval): ...
194
+ async def get(self, agent_url): ...
195
+ async def mark_verified(self, agent_url): ...
196
+ async def unregister(self, agent_url): ...
197
+ async def list_available(self): ...
198
+ async def get_stale(self, multiplier=3): ...
199
+
200
+ server = AgentRegistryServer(provider=RedisRegistryProvider())
201
+ ```
202
+
203
+ ## Configuration
204
+
205
+ | Parameter | Default | Description |
206
+ |-----------|---------|-------------|
207
+ | `stale_multiplier` | `3` | Evict agent after `multiplier × interval` seconds of silence |
208
+ | `cleanup_interval` | `30` | Seconds between cleanup sweeps |
209
+ | `fetch_timeout` | `10.0` | Timeout for fetching remote agent cards |
210
+ | `interval` (client) | `60` | Heartbeat interval in seconds |
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ pip install -e ".[dev]"
216
+ pytest
217
+ ```
218
+
219
+ ## Publishing
220
+
221
+ ```bash
222
+ pip install build twine
223
+ python -m build
224
+ twine upload dist/*
225
+ ```
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,196 @@
1
+ # oo-a2a-registry
2
+
3
+ Fast and lean AI agent registry implementing the [A2A protocol](https://a2a-protocol.org/) for agent discovery via heartbeat-based registration.
4
+
5
+ Supports **A2A v0.3** (top-level `url` field, `agent.json` discovery) and **A2A v1.0** (`supportedInterfaces`, `agent-card.json` discovery).
6
+
7
+ ## Overview
8
+
9
+ | Component | What it does |
10
+ |-----------|--------------|
11
+ | **Server** | Exposes `GET /.well-known/agents` returning live A2A agent cards. Accepts `POST /registry/heartbeat` from clients. Verifies agents by fetching their well-known card (`agent-card.json` or `agent.json`). Evicts agents that miss more than 3 consecutive heartbeats. |
12
+ | **Client** | Sends the agent's `AgentCard` to the registry on a configurable interval (default 60 s) as a background task. |
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install oo-a2a-registry # client + server (no ASGI server)
18
+ pip install "oo-a2a-registry[server]" # includes uvicorn
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ### Standalone registry server
24
+
25
+ ```python
26
+ import uvicorn
27
+ from a2a_registry import AgentRegistryServer
28
+
29
+ server = AgentRegistryServer()
30
+ app = server.create_app()
31
+
32
+ if __name__ == "__main__":
33
+ uvicorn.run(app, host="0.0.0.0", port=8000)
34
+ ```
35
+
36
+ ### Mount into an existing FastAPI app
37
+
38
+ ```python
39
+ from fastapi import FastAPI
40
+ from a2a_registry import AgentRegistryServer
41
+
42
+ app = FastAPI()
43
+ registry = AgentRegistryServer()
44
+ registry.setup(app) # adds /.well-known/agents and /registry/heartbeat
45
+ ```
46
+
47
+ ### Agent client — A2A v0.3
48
+
49
+ ```python
50
+ import asyncio
51
+ from a2a_registry import AgentCard, RegistryClient
52
+
53
+ card = AgentCard(
54
+ name="My Agent",
55
+ url="http://my-agent:8080", # agent must serve /.well-known/agent.json
56
+ version="1.0.0",
57
+ )
58
+
59
+ async def main():
60
+ async with RegistryClient("http://registry:8000", card, interval=30) as client:
61
+ await asyncio.sleep(300) # heartbeats run in the background
62
+
63
+ asyncio.run(main())
64
+ ```
65
+
66
+ ### Agent client — A2A v1.0
67
+
68
+ ```python
69
+ import asyncio
70
+ from a2a_registry import AgentCard, AgentInterface, RegistryClient
71
+
72
+ card = AgentCard(
73
+ name="My Agent",
74
+ supportedInterfaces=[
75
+ AgentInterface(
76
+ url="http://my-agent:8080/jsonrpc",
77
+ protocolBinding="json-rpc/2.0",
78
+ protocolVersion="1.0",
79
+ )
80
+ ],
81
+ version="1.0.0",
82
+ )
83
+
84
+ async def main():
85
+ async with RegistryClient("http://registry:8000", card, interval=30) as client:
86
+ await asyncio.sleep(300)
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ ### Send a single heartbeat
92
+
93
+ ```python
94
+ client = RegistryClient("http://registry:8000", card)
95
+ result = await client.send_once()
96
+ print(result.verified)
97
+ ```
98
+
99
+ ## Examples
100
+
101
+ Runnable examples are in the [`examples/`](examples/) directory:
102
+
103
+ | File | Description |
104
+ |------|-------------|
105
+ | [`registry_server.py`](examples/registry_server.py) | Standalone registry on port 8000 |
106
+ | [`hello_world_agent.py`](examples/hello_world_agent.py) | Hello World agent (A2A v0.3) on port 8001 |
107
+ | [`hello_world_agent_v1.py`](examples/hello_world_agent_v1.py) | Hello World agent (A2A v1.0) on port 8002 |
108
+
109
+ ```bash
110
+ pip install "oo-a2a-registry[server]"
111
+ python examples/registry_server.py &
112
+ python examples/hello_world_agent.py &
113
+ curl -s http://localhost:8000/.well-known/agents | python -m json.tool
114
+ ```
115
+
116
+ ## A2A version compatibility
117
+
118
+ The registry accepts agent cards from both protocol versions and auto-detects the discovery path.
119
+
120
+ | | A2A v0.3 | A2A v1.0 |
121
+ |---|---|---|
122
+ | Agent URL field | top-level `url` | `supportedInterfaces[].url` |
123
+ | Discovery path | `/.well-known/agent.json` | `/.well-known/agent-card.json` |
124
+ | Protocol version | `protocolVersion` (card-level) | `supportedInterfaces[].protocolVersion` |
125
+
126
+ Cards without a top-level `url` must provide `supportedInterfaces`. The registry uses `AgentCard.effective_url` as the canonical key (returns `url` for v0.3, `supportedInterfaces[0].url` for v1.0).
127
+
128
+ When verifying an agent the registry tries the preferred discovery path first (based on which fields the heartbeat card has) and falls back to the other automatically.
129
+
130
+ ## How it works
131
+
132
+ ```
133
+ Agent Registry Server
134
+ | |
135
+ |-- POST /registry/heartbeat ----> |
136
+ | { agent_card, interval } |
137
+ | |-- GET {origin}/.well-known/agent-card.json (v1.0)
138
+ | | — or —
139
+ | |-- GET {origin}/.well-known/agent.json (v0.3)
140
+ | | (once, to verify reachability)
141
+ | |-- store as "available"
142
+ |<-- { status: "ok", verified } -- |
143
+ | |
144
+ |-- POST /registry/heartbeat ----> | (every `interval` seconds)
145
+ | |-- refresh last_heartbeat timestamp
146
+ | |
147
+ | (silence > 3 × interval) |-- evict from registry
148
+ ```
149
+
150
+ `GET /.well-known/agents` returns only agents with `status == available`.
151
+
152
+ ## Custom storage backend
153
+
154
+ Implement `RegistryProvider` to plug in Redis, a database, etc.:
155
+
156
+ ```python
157
+ from a2a_registry.server import RegistryProvider
158
+
159
+ class RedisRegistryProvider(RegistryProvider):
160
+ async def upsert(self, agent_card, interval): ...
161
+ async def get(self, agent_url): ...
162
+ async def mark_verified(self, agent_url): ...
163
+ async def unregister(self, agent_url): ...
164
+ async def list_available(self): ...
165
+ async def get_stale(self, multiplier=3): ...
166
+
167
+ server = AgentRegistryServer(provider=RedisRegistryProvider())
168
+ ```
169
+
170
+ ## Configuration
171
+
172
+ | Parameter | Default | Description |
173
+ |-----------|---------|-------------|
174
+ | `stale_multiplier` | `3` | Evict agent after `multiplier × interval` seconds of silence |
175
+ | `cleanup_interval` | `30` | Seconds between cleanup sweeps |
176
+ | `fetch_timeout` | `10.0` | Timeout for fetching remote agent cards |
177
+ | `interval` (client) | `60` | Heartbeat interval in seconds |
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ pip install -e ".[dev]"
183
+ pytest
184
+ ```
185
+
186
+ ## Publishing
187
+
188
+ ```bash
189
+ pip install build twine
190
+ python -m build
191
+ twine upload dist/*
192
+ ```
193
+
194
+ ## License
195
+
196
+ MIT