agent-core-inbound 0.8.2__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.
- agent_core_inbound-0.8.2/.gitignore +245 -0
- agent_core_inbound-0.8.2/CHANGELOG.md +31 -0
- agent_core_inbound-0.8.2/PKG-INFO +132 -0
- agent_core_inbound-0.8.2/README.md +114 -0
- agent_core_inbound-0.8.2/VERSION +1 -0
- agent_core_inbound-0.8.2/pyproject.toml +45 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/__init__.py +4 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/_path.py +39 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/audit.py +67 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/endpoint.py +214 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/funnel_handler.py +126 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/github_allowance.py +95 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/github_connector.py +218 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/github_event.py +33 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/plugin.py +20 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/protocol.py +42 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/router.py +228 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/testing/__init__.py +9 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/testing/fake_connector.py +63 -0
- agent_core_inbound-0.8.2/src/agent_core_inbound/types.py +65 -0
- agent_core_inbound-0.8.2/tests/__init__.py +0 -0
- agent_core_inbound-0.8.2/tests/test_audit.py +78 -0
- agent_core_inbound-0.8.2/tests/test_endpoint_deliver.py +90 -0
- agent_core_inbound-0.8.2/tests/test_endpoint_integration.py +258 -0
- agent_core_inbound-0.8.2/tests/test_endpoint_protocol.py +40 -0
- agent_core_inbound-0.8.2/tests/test_funnel_handler.py +258 -0
- agent_core_inbound-0.8.2/tests/test_funnel_http.py +199 -0
- agent_core_inbound-0.8.2/tests/test_github_allowance.py +220 -0
- agent_core_inbound-0.8.2/tests/test_github_connector.py +693 -0
- agent_core_inbound-0.8.2/tests/test_inbound_publish_task_lifecycle.py +94 -0
- agent_core_inbound-0.8.2/tests/test_path.py +46 -0
- agent_core_inbound-0.8.2/tests/test_protocol.py +51 -0
- agent_core_inbound-0.8.2/tests/test_router.py +382 -0
- agent_core_inbound-0.8.2/tests/test_types.py +51 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Memory compiler runtime state
|
|
2
|
+
memory-compiler/daily/
|
|
3
|
+
memory-compiler/reports/
|
|
4
|
+
memory-compiler/scripts/state.json
|
|
5
|
+
memory-compiler/scripts/last-flush.json
|
|
6
|
+
memory-compiler/scripts/flush.log
|
|
7
|
+
memory-compiler/scripts/compile.log
|
|
8
|
+
memory-compiler/scripts/session-flush-*.md
|
|
9
|
+
memory-compiler/scripts/flush-context-*.md
|
|
10
|
+
|
|
11
|
+
# Handoff writer state (placed alongside handoff files)
|
|
12
|
+
**/handoff-state.json
|
|
13
|
+
|
|
14
|
+
# Git worktrees (superpowers / parallel feature work)
|
|
15
|
+
.worktrees/
|
|
16
|
+
# Native (harness) worktrees
|
|
17
|
+
.claude/worktrees/
|
|
18
|
+
|
|
19
|
+
# Superpowers brainstorm mockups / scratch
|
|
20
|
+
.superpowers/
|
|
21
|
+
|
|
22
|
+
# Agent-core runtime state (handoffs, bus.sqlite, transcripts, etc.)
|
|
23
|
+
.agent-core/
|
|
24
|
+
|
|
25
|
+
# Local scratch / smoke-test artifacts
|
|
26
|
+
.tmp/
|
|
27
|
+
|
|
28
|
+
# Claude Code per-machine local overrides
|
|
29
|
+
.claude/settings.local.json
|
|
30
|
+
.claude/*.lock
|
|
31
|
+
|
|
32
|
+
# Byte-compiled / optimized / DLL files
|
|
33
|
+
__pycache__/
|
|
34
|
+
*.py[codz]
|
|
35
|
+
*$py.class
|
|
36
|
+
|
|
37
|
+
# C extensions
|
|
38
|
+
*.so
|
|
39
|
+
|
|
40
|
+
# Distribution / packaging
|
|
41
|
+
.Python
|
|
42
|
+
build/
|
|
43
|
+
develop-eggs/
|
|
44
|
+
dist/
|
|
45
|
+
downloads/
|
|
46
|
+
eggs/
|
|
47
|
+
.eggs/
|
|
48
|
+
lib/
|
|
49
|
+
lib64/
|
|
50
|
+
parts/
|
|
51
|
+
sdist/
|
|
52
|
+
var/
|
|
53
|
+
wheels/
|
|
54
|
+
share/python-wheels/
|
|
55
|
+
*.egg-info/
|
|
56
|
+
.installed.cfg
|
|
57
|
+
*.egg
|
|
58
|
+
MANIFEST
|
|
59
|
+
|
|
60
|
+
# PyInstaller
|
|
61
|
+
# Usually these files are written by a python script from a template
|
|
62
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
63
|
+
*.manifest
|
|
64
|
+
*.spec
|
|
65
|
+
|
|
66
|
+
# Installer logs
|
|
67
|
+
pip-log.txt
|
|
68
|
+
pip-delete-this-directory.txt
|
|
69
|
+
|
|
70
|
+
# Unit test / coverage reports
|
|
71
|
+
htmlcov/
|
|
72
|
+
.tox/
|
|
73
|
+
.nox/
|
|
74
|
+
.coverage
|
|
75
|
+
.coverage.*
|
|
76
|
+
.cache
|
|
77
|
+
nosetests.xml
|
|
78
|
+
coverage.xml
|
|
79
|
+
*.cover
|
|
80
|
+
*.py.cover
|
|
81
|
+
.hypothesis/
|
|
82
|
+
.pytest_cache/
|
|
83
|
+
cover/
|
|
84
|
+
|
|
85
|
+
# Translations
|
|
86
|
+
*.mo
|
|
87
|
+
*.pot
|
|
88
|
+
|
|
89
|
+
# Django stuff:
|
|
90
|
+
*.log
|
|
91
|
+
local_settings.py
|
|
92
|
+
db.sqlite3
|
|
93
|
+
db.sqlite3-journal
|
|
94
|
+
|
|
95
|
+
# Flask stuff:
|
|
96
|
+
instance/
|
|
97
|
+
.webassets-cache
|
|
98
|
+
|
|
99
|
+
# Scrapy stuff:
|
|
100
|
+
.scrapy
|
|
101
|
+
|
|
102
|
+
# Sphinx documentation
|
|
103
|
+
docs/_build/
|
|
104
|
+
|
|
105
|
+
# PyBuilder
|
|
106
|
+
.pybuilder/
|
|
107
|
+
target/
|
|
108
|
+
|
|
109
|
+
# Jupyter Notebook
|
|
110
|
+
.ipynb_checkpoints
|
|
111
|
+
|
|
112
|
+
# IPython
|
|
113
|
+
profile_default/
|
|
114
|
+
ipython_config.py
|
|
115
|
+
|
|
116
|
+
# pyenv
|
|
117
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
118
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
119
|
+
# .python-version
|
|
120
|
+
|
|
121
|
+
# pipenv
|
|
122
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
123
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
124
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
125
|
+
# install all needed dependencies.
|
|
126
|
+
#Pipfile.lock
|
|
127
|
+
|
|
128
|
+
# UV
|
|
129
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
130
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
131
|
+
# commonly ignored for libraries.
|
|
132
|
+
#uv.lock
|
|
133
|
+
|
|
134
|
+
# poetry
|
|
135
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
136
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
137
|
+
# commonly ignored for libraries.
|
|
138
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
139
|
+
#poetry.lock
|
|
140
|
+
#poetry.toml
|
|
141
|
+
|
|
142
|
+
# pdm
|
|
143
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
144
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
145
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
146
|
+
#pdm.lock
|
|
147
|
+
#pdm.toml
|
|
148
|
+
.pdm-python
|
|
149
|
+
.pdm-build/
|
|
150
|
+
|
|
151
|
+
# pixi
|
|
152
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
153
|
+
#pixi.lock
|
|
154
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
155
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
156
|
+
.pixi
|
|
157
|
+
|
|
158
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
159
|
+
__pypackages__/
|
|
160
|
+
|
|
161
|
+
# Celery stuff
|
|
162
|
+
celerybeat-schedule
|
|
163
|
+
celerybeat.pid
|
|
164
|
+
|
|
165
|
+
# SageMath parsed files
|
|
166
|
+
*.sage.py
|
|
167
|
+
|
|
168
|
+
# Environments
|
|
169
|
+
.env
|
|
170
|
+
.envrc
|
|
171
|
+
.venv
|
|
172
|
+
env/
|
|
173
|
+
venv/
|
|
174
|
+
ENV/
|
|
175
|
+
env.bak/
|
|
176
|
+
venv.bak/
|
|
177
|
+
|
|
178
|
+
# Spyder project settings
|
|
179
|
+
.spyderproject
|
|
180
|
+
.spyproject
|
|
181
|
+
|
|
182
|
+
# Rope project settings
|
|
183
|
+
.ropeproject
|
|
184
|
+
|
|
185
|
+
# mkdocs documentation
|
|
186
|
+
/site
|
|
187
|
+
|
|
188
|
+
# mypy
|
|
189
|
+
.mypy_cache/
|
|
190
|
+
.dmypy.json
|
|
191
|
+
dmypy.json
|
|
192
|
+
|
|
193
|
+
# Pyre type checker
|
|
194
|
+
.pyre/
|
|
195
|
+
|
|
196
|
+
# pytype static type analyzer
|
|
197
|
+
.pytype/
|
|
198
|
+
|
|
199
|
+
# Cython debug symbols
|
|
200
|
+
cython_debug/
|
|
201
|
+
|
|
202
|
+
# PyCharm
|
|
203
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
204
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
205
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
206
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
207
|
+
#.idea/
|
|
208
|
+
|
|
209
|
+
# Abstra
|
|
210
|
+
# Abstra is an AI-powered process automation framework.
|
|
211
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
212
|
+
# Learn more at https://abstra.io/docs
|
|
213
|
+
.abstra/
|
|
214
|
+
|
|
215
|
+
# Visual Studio Code
|
|
216
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
217
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
218
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
219
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
220
|
+
# .vscode/
|
|
221
|
+
|
|
222
|
+
# Ruff stuff:
|
|
223
|
+
.ruff_cache/
|
|
224
|
+
|
|
225
|
+
# PyPI configuration file
|
|
226
|
+
.pypirc
|
|
227
|
+
|
|
228
|
+
# Cursor
|
|
229
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
230
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
231
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
232
|
+
.cursorignore
|
|
233
|
+
.cursorindexingignore
|
|
234
|
+
|
|
235
|
+
# Marimo
|
|
236
|
+
marimo/_static/
|
|
237
|
+
marimo/_lsp/
|
|
238
|
+
__marimo__/
|
|
239
|
+
.gstack/
|
|
240
|
+
|
|
241
|
+
# macOS Finder metadata (occasionally sneaks in via vendored sources)
|
|
242
|
+
.DS_Store
|
|
243
|
+
|
|
244
|
+
# Operational docs that must not live in source control
|
|
245
|
+
pepper-vault-plan.txt
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.8.2](https://github.com/jeffrichley/agent_core/compare/v0.8.1...agent-core-inbound-v0.8.2) (2026-07-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* republish package family to complete rate-limited first publish ([#477](https://github.com/jeffrichley/agent_core/issues/477)) ([32977ac](https://github.com/jeffrichley/agent_core/commit/32977ac78da4a15553329bb4e1fdaa9e894834ad))
|
|
9
|
+
|
|
10
|
+
## [0.8.1](https://github.com/jeffrichley/agent_core/compare/v0.8.0...agent-core-inbound-v0.8.1) (2026-07-22)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* rename flagship dist to agent-core-bus + bump inter-package pins to 0.8.x ([#474](https://github.com/jeffrichley/agent_core/issues/474)) ([48426e7](https://github.com/jeffrichley/agent_core/commit/48426e7ff753d8f35d4a4626308546e9d0f331f7))
|
|
16
|
+
|
|
17
|
+
## [0.8.0](https://github.com/jeffrichley/agent_core/compare/v0.7.0...agent-core-inbound-v0.8.0) (2026-07-21)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **deps:** version-pin sibling deps and remove qwen-tts from voice ([#430](https://github.com/jeffrichley/agent_core/issues/430)) ([3b173ee](https://github.com/jeffrichley/agent_core/commit/3b173eebd24fb8eab995785a9e154a0dda870f19))
|
|
23
|
+
* **hatchery:** ungendered templates + .mcp.json generation ([#81](https://github.com/jeffrichley/agent_core/issues/81)/[#82](https://github.com/jeffrichley/agent_core/issues/82)) ([#357](https://github.com/jeffrichley/agent_core/issues/357)) ([f48762d](https://github.com/jeffrichley/agent_core/commit/f48762df78eb12cfa9524e9b5a277741534ea8a7)), closes [#311](https://github.com/jeffrichley/agent_core/issues/311)
|
|
24
|
+
* **release:** configure release-please for 12-package synchronized version train ([#428](https://github.com/jeffrichley/agent_core/issues/428)) ([c1b4097](https://github.com/jeffrichley/agent_core/commit/c1b409765c61d09a6657b0e8b5e43a27cb235b3e))
|
|
25
|
+
* **secrets:** add vault-API accessor and scrub subprocess env ([#399](https://github.com/jeffrichley/agent_core/issues/399)) ([adb404b](https://github.com/jeffrichley/agent_core/commit/adb404b046bd224b56d64188b2cb56eb134d99b7))
|
|
26
|
+
* **supervision:** migrate leaky asyncio.create_task sites to BusHandle.spawn() ([#302](https://github.com/jeffrichley/agent_core/issues/302)) ([0716587](https://github.com/jeffrichley/agent_core/commit/07165879a4f1055ff1d0636169bdc2a178ea57da))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **discord,inbound:** nack transient failures instead of acking (issue [#275](https://github.com/jeffrichley/agent_core/issues/275)) ([#281](https://github.com/jeffrichley/agent_core/issues/281)) ([ad52bd6](https://github.com/jeffrichley/agent_core/commit/ad52bd6eb883f1706e76df32260317767f697b2d))
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-core-inbound
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: Inbound notifications router — deny-by-default external-event surface for agent-core beings.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: agent-core-bus<0.9,>=0.8
|
|
8
|
+
Requires-Dist: agent-core-credentials<0.9,>=0.8
|
|
9
|
+
Requires-Dist: fastapi>=0.110
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
11
|
+
Requires-Dist: uvicorn>=0.27
|
|
12
|
+
Requires-Dist: watchdog>=4.0
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: httpx; extra == 'test'
|
|
15
|
+
Requires-Dist: pytest; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest-asyncio; extra == 'test'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# agent-core-inbound
|
|
20
|
+
|
|
21
|
+
Deny-by-default inbound notifications router for agent-core beings.
|
|
22
|
+
External signals (GitHub webhooks, Gmail messages, calendar events)
|
|
23
|
+
flow through per-source **connectors** that classify each event as
|
|
24
|
+
`Allow{tier, reason}` or `Deny`. The router de-dupes, rate-limits,
|
|
25
|
+
delivers via the agent-core bus, and writes an audit log.
|
|
26
|
+
|
|
27
|
+
See `docs/superpowers/specs/2026-06-20-inbound-notifications-design.md`
|
|
28
|
+
in the agent_core repo for the full design.
|
|
29
|
+
|
|
30
|
+
## Bringing v1.a online (operator runbook)
|
|
31
|
+
|
|
32
|
+
### 1. Generate the GitHub webhook secret
|
|
33
|
+
|
|
34
|
+
Pick any high-entropy string; e.g.:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Set it as an env var in the daemon's environment (e.g., your `~/.agent-core/.env` or systemd unit):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
FOREMAN_GITHUB_WEBHOOK_SECRET=<paste-here>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Write your allowance file
|
|
47
|
+
|
|
48
|
+
`~/.<being>/.config/inbound/github-allowance.toml`:
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
# Schema-flexible rule shape (v2). See spec
|
|
52
|
+
# docs/superpowers/specs/2026-06-21-inbound-v2-schema-flexible-events-design.md
|
|
53
|
+
# for the full grammar.
|
|
54
|
+
|
|
55
|
+
[[allow]]
|
|
56
|
+
rule_id = "pr_review_requested_any_project"
|
|
57
|
+
event = "pull_request_review_requested"
|
|
58
|
+
match = { "requested_reviewer.login" = "<your-github-login>" }
|
|
59
|
+
tier = "red"
|
|
60
|
+
reason = "PR review requested on me"
|
|
61
|
+
|
|
62
|
+
[[allow]]
|
|
63
|
+
rule_id = "needs_help_foreman"
|
|
64
|
+
event = "issues_labeled"
|
|
65
|
+
repo = "<org>/<repo>"
|
|
66
|
+
match = { "label.name" = "foreman:needs-help" }
|
|
67
|
+
tier = "red"
|
|
68
|
+
reason = "Foreman escalation — needs operator unstick"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `reviewer`/`label_name` shortcuts from v1.a still work — they translate to `match` entries automatically. `body_contains` was removed in v2 (raises `ValueError` on load); use exact-equality `match` instead, or wait for v2.1's `match_contains` operator.
|
|
72
|
+
|
|
73
|
+
**Body projection.** The GitHub connector trims the Notification envelope body to a small per-event-type field set (event type, action, repo, key identifiers). This keeps inline bus payloads under 1 KB and avoids bloating tool results with GitHub metadata your being doesn't need. The full raw webhook payload is always recoverable from GitHub's webhook delivery history via `gh api repos/<repo>/hooks/<id>/deliveries/<delivery_id>`.
|
|
74
|
+
|
|
75
|
+
The router watches the file's mtime and reloads on every webhook delivery — edit the TOML and the next event picks up the new rules without restarting the daemon.
|
|
76
|
+
|
|
77
|
+
### 3. Register the endpoint in agent_core.yaml
|
|
78
|
+
|
|
79
|
+
The inbound-notifications endpoint registers via the `agent_core` pluggy hook (see `agent_core_inbound/plugin.py` — the `inbound.github` type is registered automatically once the package is installed).
|
|
80
|
+
|
|
81
|
+
Add this entry to your `agent_core.yaml`'s `endpoints:` list:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
endpoints:
|
|
85
|
+
- type: inbound.github
|
|
86
|
+
name: inbound
|
|
87
|
+
params:
|
|
88
|
+
target_being: <your-being>
|
|
89
|
+
listen_host: 127.0.0.1
|
|
90
|
+
listen_port: 8765
|
|
91
|
+
webhook_secret_env: FOREMAN_GITHUB_WEBHOOK_SECRET
|
|
92
|
+
github_allowance_path: ~/.<being>/.config/inbound/github-allowance.toml
|
|
93
|
+
audit_log_path: ~/.<being>/state/inbound-audit.jsonl
|
|
94
|
+
rate_limit_per_minute: 30
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The runner reads each entry's `type` and looks it up in the pluggy-registered endpoint types map. The `name` is the bus addressing name (also surfaces in `agent-core ps`). All `params` are passed as constructor kwargs to `InboundEndpoint`.
|
|
98
|
+
|
|
99
|
+
### 4. Start Tailscale Funnel
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
tailscale funnel 8765
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Note the issued `https://router.<tailnet>.ts.net` URL.
|
|
106
|
+
|
|
107
|
+
### 5. Configure the GitHub webhook
|
|
108
|
+
|
|
109
|
+
In your repo settings → Webhooks → Add webhook:
|
|
110
|
+
|
|
111
|
+
- **Payload URL:** `https://router.<tailnet>.ts.net/github`
|
|
112
|
+
- **Content type:** `application/json`
|
|
113
|
+
- **Secret:** the same value you stored in `FOREMAN_GITHUB_WEBHOOK_SECRET`
|
|
114
|
+
- **Which events:** "Let me select individual events" — check **Workflow runs**, **Pull requests**, **Pull request reviews**, **Issues**, **Issue comments**, **Pushes**, **Releases**, **Statuses**. (Schema-flexible matching means we can add more later via `gh api -X PATCH repos/<repo>/hooks/<id> -f events='[...]'` with no daemon change.)
|
|
115
|
+
|
|
116
|
+
### 6. Smoke test
|
|
117
|
+
|
|
118
|
+
On any PR in your configured repo, request a review from `@<your-github-login>`. Within ~10s:
|
|
119
|
+
|
|
120
|
+
- `~/.<being>/state/inbound-audit.jsonl` gains an `allow` line with `rule_id=pr_review_requested_any_project`.
|
|
121
|
+
- Your being's bus inbox receives a `Notification` envelope (urgency `red`).
|
|
122
|
+
|
|
123
|
+
If you instead see a `deny` line, double-check `match = { "requested_reviewer.login" = "<your-github-login>" }` in the allowance TOML against the actual reviewer GitHub login.
|
|
124
|
+
|
|
125
|
+
### Troubleshooting
|
|
126
|
+
|
|
127
|
+
- **All POSTs land 401:** the env var secret does not match the GitHub webhook secret. Re-paste both ends.
|
|
128
|
+
- **`BusBootError: unknown endpoint type 'inbound.github'`:** the `agent-core-inbound` package isn't installed in the daemon's environment, or its entry point isn't being discovered. Run `uv sync` (or your install path equivalent) and confirm `python -c "import agent_core_inbound.plugin"` succeeds.
|
|
129
|
+
- **Webhook delivers but no bus envelope:** check the audit log first. If `deny` lines appear, the allowance rule isn't matching — verify the `event`, `repo`, and `match` dotted-path keys against the actual webhook payload (visible in GitHub's webhook delivery history).
|
|
130
|
+
- **No audit log writes at all:** the endpoint isn't seeing the POST. Confirm Tailscale Funnel is active (`tailscale funnel status`) and the daemon log shows `InboundEndpoint(name=inbound) started on 127.0.0.1:8765`.
|
|
131
|
+
- **`deny` with `reason="no_matching_rule"` on every event:** v2 parses every webhook event generically (no silent 204 fallback), so the connector denies anything not matched by an allow rule. If you expected an allow, the rule's `event` key (e.g. `pull_request_opened`) or `match` dotted-paths don't line up with the payload shape — check the webhook delivery body in GitHub's "Recent Deliveries" UI to confirm the exact field names.
|
|
132
|
+
- **Webhook deliveries land 404 from uvicorn:** the Tailscale Funnel command should be `tailscale funnel <port>` — do NOT use `--set-path=/github`. That flag STRIPS the path prefix before forwarding, leaving uvicorn to see `POST /` (no route). The default mount at `/` is correct because the FastAPI route is at `/github`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# agent-core-inbound
|
|
2
|
+
|
|
3
|
+
Deny-by-default inbound notifications router for agent-core beings.
|
|
4
|
+
External signals (GitHub webhooks, Gmail messages, calendar events)
|
|
5
|
+
flow through per-source **connectors** that classify each event as
|
|
6
|
+
`Allow{tier, reason}` or `Deny`. The router de-dupes, rate-limits,
|
|
7
|
+
delivers via the agent-core bus, and writes an audit log.
|
|
8
|
+
|
|
9
|
+
See `docs/superpowers/specs/2026-06-20-inbound-notifications-design.md`
|
|
10
|
+
in the agent_core repo for the full design.
|
|
11
|
+
|
|
12
|
+
## Bringing v1.a online (operator runbook)
|
|
13
|
+
|
|
14
|
+
### 1. Generate the GitHub webhook secret
|
|
15
|
+
|
|
16
|
+
Pick any high-entropy string; e.g.:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Set it as an env var in the daemon's environment (e.g., your `~/.agent-core/.env` or systemd unit):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
FOREMAN_GITHUB_WEBHOOK_SECRET=<paste-here>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Write your allowance file
|
|
29
|
+
|
|
30
|
+
`~/.<being>/.config/inbound/github-allowance.toml`:
|
|
31
|
+
|
|
32
|
+
```toml
|
|
33
|
+
# Schema-flexible rule shape (v2). See spec
|
|
34
|
+
# docs/superpowers/specs/2026-06-21-inbound-v2-schema-flexible-events-design.md
|
|
35
|
+
# for the full grammar.
|
|
36
|
+
|
|
37
|
+
[[allow]]
|
|
38
|
+
rule_id = "pr_review_requested_any_project"
|
|
39
|
+
event = "pull_request_review_requested"
|
|
40
|
+
match = { "requested_reviewer.login" = "<your-github-login>" }
|
|
41
|
+
tier = "red"
|
|
42
|
+
reason = "PR review requested on me"
|
|
43
|
+
|
|
44
|
+
[[allow]]
|
|
45
|
+
rule_id = "needs_help_foreman"
|
|
46
|
+
event = "issues_labeled"
|
|
47
|
+
repo = "<org>/<repo>"
|
|
48
|
+
match = { "label.name" = "foreman:needs-help" }
|
|
49
|
+
tier = "red"
|
|
50
|
+
reason = "Foreman escalation — needs operator unstick"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `reviewer`/`label_name` shortcuts from v1.a still work — they translate to `match` entries automatically. `body_contains` was removed in v2 (raises `ValueError` on load); use exact-equality `match` instead, or wait for v2.1's `match_contains` operator.
|
|
54
|
+
|
|
55
|
+
**Body projection.** The GitHub connector trims the Notification envelope body to a small per-event-type field set (event type, action, repo, key identifiers). This keeps inline bus payloads under 1 KB and avoids bloating tool results with GitHub metadata your being doesn't need. The full raw webhook payload is always recoverable from GitHub's webhook delivery history via `gh api repos/<repo>/hooks/<id>/deliveries/<delivery_id>`.
|
|
56
|
+
|
|
57
|
+
The router watches the file's mtime and reloads on every webhook delivery — edit the TOML and the next event picks up the new rules without restarting the daemon.
|
|
58
|
+
|
|
59
|
+
### 3. Register the endpoint in agent_core.yaml
|
|
60
|
+
|
|
61
|
+
The inbound-notifications endpoint registers via the `agent_core` pluggy hook (see `agent_core_inbound/plugin.py` — the `inbound.github` type is registered automatically once the package is installed).
|
|
62
|
+
|
|
63
|
+
Add this entry to your `agent_core.yaml`'s `endpoints:` list:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
endpoints:
|
|
67
|
+
- type: inbound.github
|
|
68
|
+
name: inbound
|
|
69
|
+
params:
|
|
70
|
+
target_being: <your-being>
|
|
71
|
+
listen_host: 127.0.0.1
|
|
72
|
+
listen_port: 8765
|
|
73
|
+
webhook_secret_env: FOREMAN_GITHUB_WEBHOOK_SECRET
|
|
74
|
+
github_allowance_path: ~/.<being>/.config/inbound/github-allowance.toml
|
|
75
|
+
audit_log_path: ~/.<being>/state/inbound-audit.jsonl
|
|
76
|
+
rate_limit_per_minute: 30
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The runner reads each entry's `type` and looks it up in the pluggy-registered endpoint types map. The `name` is the bus addressing name (also surfaces in `agent-core ps`). All `params` are passed as constructor kwargs to `InboundEndpoint`.
|
|
80
|
+
|
|
81
|
+
### 4. Start Tailscale Funnel
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
tailscale funnel 8765
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Note the issued `https://router.<tailnet>.ts.net` URL.
|
|
88
|
+
|
|
89
|
+
### 5. Configure the GitHub webhook
|
|
90
|
+
|
|
91
|
+
In your repo settings → Webhooks → Add webhook:
|
|
92
|
+
|
|
93
|
+
- **Payload URL:** `https://router.<tailnet>.ts.net/github`
|
|
94
|
+
- **Content type:** `application/json`
|
|
95
|
+
- **Secret:** the same value you stored in `FOREMAN_GITHUB_WEBHOOK_SECRET`
|
|
96
|
+
- **Which events:** "Let me select individual events" — check **Workflow runs**, **Pull requests**, **Pull request reviews**, **Issues**, **Issue comments**, **Pushes**, **Releases**, **Statuses**. (Schema-flexible matching means we can add more later via `gh api -X PATCH repos/<repo>/hooks/<id> -f events='[...]'` with no daemon change.)
|
|
97
|
+
|
|
98
|
+
### 6. Smoke test
|
|
99
|
+
|
|
100
|
+
On any PR in your configured repo, request a review from `@<your-github-login>`. Within ~10s:
|
|
101
|
+
|
|
102
|
+
- `~/.<being>/state/inbound-audit.jsonl` gains an `allow` line with `rule_id=pr_review_requested_any_project`.
|
|
103
|
+
- Your being's bus inbox receives a `Notification` envelope (urgency `red`).
|
|
104
|
+
|
|
105
|
+
If you instead see a `deny` line, double-check `match = { "requested_reviewer.login" = "<your-github-login>" }` in the allowance TOML against the actual reviewer GitHub login.
|
|
106
|
+
|
|
107
|
+
### Troubleshooting
|
|
108
|
+
|
|
109
|
+
- **All POSTs land 401:** the env var secret does not match the GitHub webhook secret. Re-paste both ends.
|
|
110
|
+
- **`BusBootError: unknown endpoint type 'inbound.github'`:** the `agent-core-inbound` package isn't installed in the daemon's environment, or its entry point isn't being discovered. Run `uv sync` (or your install path equivalent) and confirm `python -c "import agent_core_inbound.plugin"` succeeds.
|
|
111
|
+
- **Webhook delivers but no bus envelope:** check the audit log first. If `deny` lines appear, the allowance rule isn't matching — verify the `event`, `repo`, and `match` dotted-path keys against the actual webhook payload (visible in GitHub's webhook delivery history).
|
|
112
|
+
- **No audit log writes at all:** the endpoint isn't seeing the POST. Confirm Tailscale Funnel is active (`tailscale funnel status`) and the daemon log shows `InboundEndpoint(name=inbound) started on 127.0.0.1:8765`.
|
|
113
|
+
- **`deny` with `reason="no_matching_rule"` on every event:** v2 parses every webhook event generically (no silent 204 fallback), so the connector denies anything not matched by an allow rule. If you expected an allow, the rule's `event` key (e.g. `pull_request_opened`) or `match` dotted-paths don't line up with the payload shape — check the webhook delivery body in GitHub's "Recent Deliveries" UI to confirm the exact field names.
|
|
114
|
+
- **Webhook deliveries land 404 from uvicorn:** the Tailscale Funnel command should be `tailscale funnel <port>` — do NOT use `--set-path=/github`. That flag STRIPS the path prefix before forwarding, leaving uvicorn to see `POST /` (no route). The default mount at `/` is correct because the FastAPI route is at `/github`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.8.2
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-core-inbound"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Inbound notifications router — deny-by-default external-event surface for agent-core beings."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"agent-core-bus>=0.8,<0.9",
|
|
10
|
+
"agent-core-credentials>=0.8,<0.9",
|
|
11
|
+
"pydantic>=2.0",
|
|
12
|
+
"fastapi>=0.110",
|
|
13
|
+
"uvicorn>=0.27",
|
|
14
|
+
"watchdog>=4.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
test = [
|
|
19
|
+
"pytest",
|
|
20
|
+
"pytest-asyncio",
|
|
21
|
+
"httpx",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.entry-points."agent_core"]
|
|
25
|
+
inbound_aliases = "agent_core_inbound.plugin"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/agent_core_inbound"]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
source = "uv-dynamic-versioning"
|
|
36
|
+
|
|
37
|
+
[tool.uv-dynamic-versioning]
|
|
38
|
+
fallback-version = "0.0.0"
|
|
39
|
+
|
|
40
|
+
[tool.uv]
|
|
41
|
+
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
|
|
42
|
+
|
|
43
|
+
[tool.uv.sources]
|
|
44
|
+
agent-core = { workspace = true }
|
|
45
|
+
agent-core-credentials = { workspace = true }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Dotted-path resolver for the GitHubConnector matching engine.
|
|
2
|
+
|
|
3
|
+
Walks a nested dict by splitting a dotted path into keys. Returns the
|
|
4
|
+
sentinel ``MISSING`` for any path that doesn't resolve cleanly —
|
|
5
|
+
including paths through non-dict cursor values, missing keys, or the
|
|
6
|
+
empty path. Falsy values (False, 0, "", None, [], {}) are NOT treated
|
|
7
|
+
as missing; they're real values the resolver returns directly.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Final
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _MissingSentinel:
|
|
15
|
+
__slots__ = ()
|
|
16
|
+
|
|
17
|
+
def __repr__(self) -> str:
|
|
18
|
+
return "MISSING"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
MISSING: Final = _MissingSentinel()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_path(payload: dict[str, Any], path: str) -> Any:
|
|
25
|
+
"""Resolve a dotted path against a nested dict.
|
|
26
|
+
|
|
27
|
+
Returns ``MISSING`` for any path that doesn't fully resolve.
|
|
28
|
+
"""
|
|
29
|
+
if not path:
|
|
30
|
+
return MISSING
|
|
31
|
+
cursor: Any = payload
|
|
32
|
+
for key in path.split("."):
|
|
33
|
+
if not isinstance(cursor, dict) or key not in cursor:
|
|
34
|
+
return MISSING
|
|
35
|
+
cursor = cursor[key]
|
|
36
|
+
return cursor
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["MISSING", "resolve_path"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""JSONL audit log for inbound-notifications router.
|
|
2
|
+
|
|
3
|
+
One line per classification. Allow lines carry tier + reason +
|
|
4
|
+
rule_id; Deny lines carry only the timestamp + source + target.
|
|
5
|
+
Deny intentionally does NOT serialize the event body so a denied
|
|
6
|
+
inbound leaves no privacy-sensitive trace.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from agent_core_inbound.types import Allow
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _default_clock() -> datetime:
|
|
18
|
+
return datetime.now(UTC)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuditLog:
|
|
22
|
+
"""Append-only JSONL writer keyed by absolute path.
|
|
23
|
+
|
|
24
|
+
``clock`` is injectable so tests can pin the timestamp without
|
|
25
|
+
monkey-patching ``datetime.now``. Parent directories are created
|
|
26
|
+
on first write.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
path: Path,
|
|
33
|
+
clock: Callable[[], datetime] = _default_clock,
|
|
34
|
+
) -> None:
|
|
35
|
+
self._path = path
|
|
36
|
+
self._clock = clock
|
|
37
|
+
|
|
38
|
+
def record_allow(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
connector_name: str,
|
|
42
|
+
target_being: str,
|
|
43
|
+
verdict: Allow,
|
|
44
|
+
rule_id: str,
|
|
45
|
+
) -> None:
|
|
46
|
+
self._write({
|
|
47
|
+
"ts": self._clock().isoformat(),
|
|
48
|
+
"source": connector_name,
|
|
49
|
+
"to": target_being,
|
|
50
|
+
"verdict": "allow",
|
|
51
|
+
"tier": verdict.tier.value,
|
|
52
|
+
"rule_id": rule_id,
|
|
53
|
+
"reason": verdict.reason,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
def record_deny(self, *, connector_name: str, target_being: str) -> None:
|
|
57
|
+
self._write({
|
|
58
|
+
"ts": self._clock().isoformat(),
|
|
59
|
+
"source": connector_name,
|
|
60
|
+
"to": target_being,
|
|
61
|
+
"verdict": "deny",
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
def _write(self, entry: dict[str, Any]) -> None:
|
|
65
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
with self._path.open("a", encoding="utf-8") as fh:
|
|
67
|
+
fh.write(json.dumps(entry, separators=(",", ":")) + "\n")
|