relayforge 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.
Files changed (62) hide show
  1. relayforge-0.1.0/.env.example +2 -0
  2. relayforge-0.1.0/.github/workflows/publish.yml +24 -0
  3. relayforge-0.1.0/.gitignore +13 -0
  4. relayforge-0.1.0/LICENSE +21 -0
  5. relayforge-0.1.0/PKG-INFO +145 -0
  6. relayforge-0.1.0/README.md +113 -0
  7. relayforge-0.1.0/docs/channels.md +115 -0
  8. relayforge-0.1.0/docs/completion-audit.md +59 -0
  9. relayforge-0.1.0/docs/design.md +57 -0
  10. relayforge-0.1.0/docs/idea-evolution.md +18 -0
  11. relayforge-0.1.0/docs/live-agreement.md +46 -0
  12. relayforge-0.1.0/docs/next-agent-brief.md +85 -0
  13. relayforge-0.1.0/docs/ownership-map.md +50 -0
  14. relayforge-0.1.0/docs/product-debt.md +17 -0
  15. relayforge-0.1.0/docs/providers.md +40 -0
  16. relayforge-0.1.0/docs/publishing.md +47 -0
  17. relayforge-0.1.0/docs/research/codex-thread-routing-2026-06-26.md +150 -0
  18. relayforge-0.1.0/docs/verification-matrix.md +24 -0
  19. relayforge-0.1.0/docs/worklog.md +140 -0
  20. relayforge-0.1.0/pyproject.toml +68 -0
  21. relayforge-0.1.0/relay.config.example.json +28 -0
  22. relayforge-0.1.0/relayforge_auto_guides.pth +1 -0
  23. relayforge-0.1.0/src/relayforge/__init__.py +5 -0
  24. relayforge-0.1.0/src/relayforge/_auto_guides.py +8 -0
  25. relayforge-0.1.0/src/relayforge/agent_guides.py +92 -0
  26. relayforge-0.1.0/src/relayforge/app.py +270 -0
  27. relayforge-0.1.0/src/relayforge/cli.py +280 -0
  28. relayforge-0.1.0/src/relayforge/cli_output.py +20 -0
  29. relayforge-0.1.0/src/relayforge/cli_parser.py +120 -0
  30. relayforge-0.1.0/src/relayforge/core/__init__.py +1 -0
  31. relayforge-0.1.0/src/relayforge/core/config.py +27 -0
  32. relayforge-0.1.0/src/relayforge/core/models.py +122 -0
  33. relayforge-0.1.0/src/relayforge/core/providers.py +8 -0
  34. relayforge-0.1.0/src/relayforge/core/router.py +84 -0
  35. relayforge-0.1.0/src/relayforge/core/targets.py +15 -0
  36. relayforge-0.1.0/src/relayforge/core/validation.py +67 -0
  37. relayforge-0.1.0/src/relayforge/providers/__init__.py +1 -0
  38. relayforge-0.1.0/src/relayforge/providers/discord/__init__.py +1 -0
  39. relayforge-0.1.0/src/relayforge/providers/discord/gateway.py +136 -0
  40. relayforge-0.1.0/src/relayforge/providers/discord/rest.py +75 -0
  41. relayforge-0.1.0/src/relayforge/py.typed +1 -0
  42. relayforge-0.1.0/src/relayforge/targets/__init__.py +1 -0
  43. relayforge-0.1.0/src/relayforge/targets/codex/__init__.py +1 -0
  44. relayforge-0.1.0/src/relayforge/targets/codex/jsonl.py +73 -0
  45. relayforge-0.1.0/src/relayforge/targets/codex_app_server/__init__.py +6 -0
  46. relayforge-0.1.0/src/relayforge/targets/codex_app_server/prompt.py +21 -0
  47. relayforge-0.1.0/src/relayforge/targets/codex_app_server/target.py +159 -0
  48. relayforge-0.1.0/src/relayforge/targets/codex_app_server/transport.py +160 -0
  49. relayforge-0.1.0/tests/test_agent_guides.py +104 -0
  50. relayforge-0.1.0/tests/test_app.py +604 -0
  51. relayforge-0.1.0/tests/test_boundaries.py +16 -0
  52. relayforge-0.1.0/tests/test_cli.py +717 -0
  53. relayforge-0.1.0/tests/test_cli_parser.py +92 -0
  54. relayforge-0.1.0/tests/test_codex_app_server_target.py +207 -0
  55. relayforge-0.1.0/tests/test_codex_jsonl.py +78 -0
  56. relayforge-0.1.0/tests/test_config.py +160 -0
  57. relayforge-0.1.0/tests/test_discord_gateway.py +127 -0
  58. relayforge-0.1.0/tests/test_discord_rest.py +56 -0
  59. relayforge-0.1.0/tests/test_packaging.py +110 -0
  60. relayforge-0.1.0/tests/test_router.py +258 -0
  61. relayforge-0.1.0/tests/test_validation.py +103 -0
  62. relayforge-0.1.0/uv.lock +882 -0
@@ -0,0 +1,2 @@
1
+ DISCORD_BOT_TOKEN=
2
+ RELAY_CONFIG=relay.config.json
@@ -0,0 +1,24 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.11"
21
+ - uses: astral-sh/setup-uv@v5
22
+ - run: uv sync --extra dev
23
+ - run: uv run python -m build
24
+ - run: uv publish
@@ -0,0 +1,13 @@
1
+ .env
2
+ .venv/
3
+ .relay/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ __pycache__/
8
+ *.py[cod]
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+ *.log
13
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rohan Sharma
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,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: relayforge
3
+ Version: 0.1.0
4
+ Summary: Route chat channels to agent targets.
5
+ Project-URL: Homepage, https://github.com/AlmanacCode/relayforge
6
+ Project-URL: Issues, https://github.com/AlmanacCode/relayforge/issues
7
+ Author: Rohan Sharma
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,automation,codex,discord,relay
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Communications :: Chat
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: certifi>=2024.7.4
22
+ Requires-Dist: httpx<1,>=0.27
23
+ Requires-Dist: pydantic<3,>=2.8
24
+ Requires-Dist: websockets<15,>=13
25
+ Provides-Extra: dev
26
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
28
+ Requires-Dist: pytest<9,>=8.2; extra == 'dev'
29
+ Requires-Dist: ruff<1,>=0.8; extra == 'dev'
30
+ Requires-Dist: twine<7,>=5.1; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Relayforge
34
+
35
+ Relayforge routes chat channels to agent targets.
36
+
37
+ The first provider is Discord. The first targets are a Codex app-server binding
38
+ and a Codex JSONL fallback inbox. The package is Python and PyPI-ready, with
39
+ provider-neutral core types so Slack, WhatsApp, and other agent APIs can be
40
+ added later.
41
+
42
+ ## Install
43
+
44
+ After the package is published:
45
+
46
+ ```bash
47
+ pip install relayforge
48
+ ```
49
+
50
+ The installed CLI command is `relayforge`.
51
+
52
+ Installed wheels add a small managed Relayforge block to:
53
+
54
+ - `~/.codex/AGENTS.md`
55
+ - `~/.claude/CLAUDE.md`
56
+
57
+ Set `RELAYFORGE_SKIP_AGENT_GUIDES=1` before starting Python with the installed
58
+ package on `sys.path` to skip this.
59
+
60
+ From this checkout:
61
+
62
+ ```bash
63
+ python -m venv .venv
64
+ . .venv/bin/activate
65
+ pip install -e '.[dev]'
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ ```json
71
+ {
72
+ "channels": [
73
+ {
74
+ "name": "rohan-codex",
75
+ "provider": "discord",
76
+ "id": "1520170530148192466",
77
+ "owner": "rohan"
78
+ }
79
+ ],
80
+ "bindings": [
81
+ {
82
+ "name": "rohan-almanac-main",
83
+ "source": "rohan-codex",
84
+ "target": "codex-app-server",
85
+ "codexThreadId": "replace-with-codex-thread-id",
86
+ "cwd": "/Users/rohan/Desktop/Projects/almanac",
87
+ "machine": "replace-with-hostname",
88
+ "transport": "codex app-server --config mcp_servers={} --listen stdio://"
89
+ }
90
+ ],
91
+ "routes": [
92
+ {
93
+ "channel": "rohan-codex",
94
+ "target": "codex-jsonl",
95
+ "inboxPath": ".relay/rohan-codex-inbox.jsonl"
96
+ }
97
+ ]
98
+ }
99
+ ```
100
+
101
+ Each founder can use the same Discord bot token with a different named channel.
102
+ A binding delivers to that founder's Codex thread on the right machine. A route
103
+ keeps JSONL fallback delivery available when the direct target fails.
104
+
105
+ See [docs/channels.md](docs/channels.md) for the multi-founder pattern.
106
+ See [docs/research/codex-thread-routing-2026-06-26.md](docs/research/codex-thread-routing-2026-06-26.md)
107
+ for the Codex thread binding direction.
108
+
109
+ ## Commands
110
+
111
+ ```bash
112
+ export DISCORD_BOT_TOKEN=...
113
+
114
+ relayforge init --config relay.config.json
115
+ relayforge channel-set --name rohan-codex --provider discord --id 1520...
116
+ relayforge route-set --channel rohan-codex --target codex-jsonl --inbox-path .relay/rohan-codex-inbox.jsonl
117
+ relayforge binding-set --name rohan-almanac-main --source rohan-codex --target codex-app-server --codex-thread-id 019f... --cwd /Users/rohan/Desktop/Projects/almanac --transport "codex app-server --config mcp_servers={} --listen stdio://"
118
+ relayforge check --config relay.config.json
119
+ relayforge listen --config relay.config.json
120
+ relayforge listen --config relay.config.json --include-bots --max-messages 1 --timeout 20
121
+ relayforge check --config relay.config.json --json
122
+ relayforge channels --config relay.config.json
123
+ relayforge routes --config relay.config.json
124
+ relayforge bindings --config relay.config.json
125
+ relayforge codex-smoke --binding rohan-almanac-main --message "smoke test"
126
+ relayforge binding-send --binding rohan-almanac-main --message "reply in Discord"
127
+ relayforge send --channel rohan-codex --message "hello"
128
+ relayforge read --channel rohan-codex --limit 10
129
+ relayforge inbox --path .relay/rohan-codex-inbox.jsonl --limit 10
130
+ relayforge inbox --path .relay/rohan-codex-inbox.jsonl --cursor .relay/rohan.cursor.json --ack
131
+ ```
132
+
133
+ Raw Discord channel IDs work for `read` and `send` even before a config file
134
+ exists. Named channels require the config.
135
+
136
+ The `listen` command uses Discord Gateway events. That means Discord pushes new
137
+ messages to this process instead of the process polling the channel.
138
+
139
+ By default, Discord bot-authored messages are ignored to avoid loops. Use
140
+ `--include-bots` only for controlled smoke tests or deliberate bot-to-agent
141
+ routing.
142
+
143
+ `codex-smoke` sends a synthetic message through a configured Codex app-server
144
+ binding. Use it only with a rollout-backed Codex thread that is safe to receive
145
+ a test turn.
@@ -0,0 +1,113 @@
1
+ # Relayforge
2
+
3
+ Relayforge routes chat channels to agent targets.
4
+
5
+ The first provider is Discord. The first targets are a Codex app-server binding
6
+ and a Codex JSONL fallback inbox. The package is Python and PyPI-ready, with
7
+ provider-neutral core types so Slack, WhatsApp, and other agent APIs can be
8
+ added later.
9
+
10
+ ## Install
11
+
12
+ After the package is published:
13
+
14
+ ```bash
15
+ pip install relayforge
16
+ ```
17
+
18
+ The installed CLI command is `relayforge`.
19
+
20
+ Installed wheels add a small managed Relayforge block to:
21
+
22
+ - `~/.codex/AGENTS.md`
23
+ - `~/.claude/CLAUDE.md`
24
+
25
+ Set `RELAYFORGE_SKIP_AGENT_GUIDES=1` before starting Python with the installed
26
+ package on `sys.path` to skip this.
27
+
28
+ From this checkout:
29
+
30
+ ```bash
31
+ python -m venv .venv
32
+ . .venv/bin/activate
33
+ pip install -e '.[dev]'
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ ```json
39
+ {
40
+ "channels": [
41
+ {
42
+ "name": "rohan-codex",
43
+ "provider": "discord",
44
+ "id": "1520170530148192466",
45
+ "owner": "rohan"
46
+ }
47
+ ],
48
+ "bindings": [
49
+ {
50
+ "name": "rohan-almanac-main",
51
+ "source": "rohan-codex",
52
+ "target": "codex-app-server",
53
+ "codexThreadId": "replace-with-codex-thread-id",
54
+ "cwd": "/Users/rohan/Desktop/Projects/almanac",
55
+ "machine": "replace-with-hostname",
56
+ "transport": "codex app-server --config mcp_servers={} --listen stdio://"
57
+ }
58
+ ],
59
+ "routes": [
60
+ {
61
+ "channel": "rohan-codex",
62
+ "target": "codex-jsonl",
63
+ "inboxPath": ".relay/rohan-codex-inbox.jsonl"
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ Each founder can use the same Discord bot token with a different named channel.
70
+ A binding delivers to that founder's Codex thread on the right machine. A route
71
+ keeps JSONL fallback delivery available when the direct target fails.
72
+
73
+ See [docs/channels.md](docs/channels.md) for the multi-founder pattern.
74
+ See [docs/research/codex-thread-routing-2026-06-26.md](docs/research/codex-thread-routing-2026-06-26.md)
75
+ for the Codex thread binding direction.
76
+
77
+ ## Commands
78
+
79
+ ```bash
80
+ export DISCORD_BOT_TOKEN=...
81
+
82
+ relayforge init --config relay.config.json
83
+ relayforge channel-set --name rohan-codex --provider discord --id 1520...
84
+ relayforge route-set --channel rohan-codex --target codex-jsonl --inbox-path .relay/rohan-codex-inbox.jsonl
85
+ relayforge binding-set --name rohan-almanac-main --source rohan-codex --target codex-app-server --codex-thread-id 019f... --cwd /Users/rohan/Desktop/Projects/almanac --transport "codex app-server --config mcp_servers={} --listen stdio://"
86
+ relayforge check --config relay.config.json
87
+ relayforge listen --config relay.config.json
88
+ relayforge listen --config relay.config.json --include-bots --max-messages 1 --timeout 20
89
+ relayforge check --config relay.config.json --json
90
+ relayforge channels --config relay.config.json
91
+ relayforge routes --config relay.config.json
92
+ relayforge bindings --config relay.config.json
93
+ relayforge codex-smoke --binding rohan-almanac-main --message "smoke test"
94
+ relayforge binding-send --binding rohan-almanac-main --message "reply in Discord"
95
+ relayforge send --channel rohan-codex --message "hello"
96
+ relayforge read --channel rohan-codex --limit 10
97
+ relayforge inbox --path .relay/rohan-codex-inbox.jsonl --limit 10
98
+ relayforge inbox --path .relay/rohan-codex-inbox.jsonl --cursor .relay/rohan.cursor.json --ack
99
+ ```
100
+
101
+ Raw Discord channel IDs work for `read` and `send` even before a config file
102
+ exists. Named channels require the config.
103
+
104
+ The `listen` command uses Discord Gateway events. That means Discord pushes new
105
+ messages to this process instead of the process polling the channel.
106
+
107
+ By default, Discord bot-authored messages are ignored to avoid loops. Use
108
+ `--include-bots` only for controlled smoke tests or deliberate bot-to-agent
109
+ routing.
110
+
111
+ `codex-smoke` sends a synthetic message through a configured Codex app-server
112
+ binding. Use it only with a rollout-backed Codex thread that is safe to receive
113
+ a test turn.
@@ -0,0 +1,115 @@
1
+ # Channels
2
+
3
+ Channels are named routing endpoints. A channel belongs to a provider and has a
4
+ provider-specific ID.
5
+
6
+ The shared Discord bot token is not tied to one person. Each founder gets a
7
+ separate channel entry. A binding maps that channel to one machine-local Codex
8
+ thread; a route can remain as the JSONL fallback.
9
+
10
+ ```json
11
+ {
12
+ "channels": [
13
+ {
14
+ "name": "rohan-codex",
15
+ "provider": "discord",
16
+ "id": "1520170530148192466",
17
+ "owner": "rohan"
18
+ },
19
+ {
20
+ "name": "kush-codex",
21
+ "provider": "discord",
22
+ "id": "replace-with-kush-channel-id",
23
+ "owner": "kush"
24
+ }
25
+ ],
26
+ "bindings": [
27
+ {
28
+ "name": "rohan-almanac-main",
29
+ "source": "rohan-codex",
30
+ "target": "codex-app-server",
31
+ "codexThreadId": "replace-with-codex-thread-id",
32
+ "cwd": "/Users/rohan/Desktop/Projects/almanac",
33
+ "machine": "replace-with-hostname"
34
+ }
35
+ ],
36
+ "routes": [
37
+ {
38
+ "channel": "rohan-codex",
39
+ "target": "codex-jsonl",
40
+ "inboxPath": ".relay/rohan-codex-inbox.jsonl"
41
+ },
42
+ {
43
+ "channel": "kush-codex",
44
+ "target": "codex-jsonl",
45
+ "inboxPath": ".relay/kush-codex-inbox.jsonl"
46
+ }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ Use `relayforge channels --config relay.config.json` to inspect configured
52
+ channels. This command does not need Discord credentials because it only reads
53
+ local config.
54
+
55
+ Use `relayforge bindings --config relay.config.json` to inspect which channels
56
+ are bound to agent targets.
57
+
58
+ Use `relayforge init` once when no config exists yet:
59
+
60
+ ```bash
61
+ relayforge init --config relay.config.json
62
+ ```
63
+
64
+ Use `relayforge channel-set` to create or update a channel without
65
+ hand-editing JSON:
66
+
67
+ ```bash
68
+ relayforge channel-set \
69
+ --name rohan-codex \
70
+ --provider discord \
71
+ --id 1520170530148192466 \
72
+ --owner rohan
73
+ ```
74
+
75
+ Use `relayforge route-set` to create or update the JSONL fallback route:
76
+
77
+ ```bash
78
+ relayforge route-set \
79
+ --channel rohan-codex \
80
+ --target codex-jsonl \
81
+ --inbox-path .relay/rohan-codex-inbox.jsonl
82
+ ```
83
+
84
+ Use `relayforge binding-set` to create or update a binding:
85
+
86
+ ```bash
87
+ relayforge binding-set \
88
+ --name rohan-almanac-main \
89
+ --source rohan-codex \
90
+ --target codex-app-server \
91
+ --codex-thread-id 019f05b3-575b-7eb2-a5c3-ff91266b4815 \
92
+ --cwd /Users/rohan/Desktop/Projects/almanac
93
+ ```
94
+
95
+ Use `relayforge codex-smoke --binding rohan-almanac-main` to verify that a
96
+ configured Codex app-server binding can resume its thread and start a test turn.
97
+ Run it only against a rollout-backed Codex thread that can safely receive a
98
+ smoke message.
99
+
100
+ Commands that talk to providers accept either a named channel or a raw provider
101
+ ID:
102
+
103
+ ```bash
104
+ relayforge read --channel rohan-codex
105
+ relayforge send --channel rohan-codex --message "hello"
106
+ ```
107
+
108
+ Raw provider IDs do not require a config file. Named channels do.
109
+
110
+ Agents can send through a configured binding without knowing the provider
111
+ channel ID:
112
+
113
+ ```bash
114
+ relayforge binding-send --binding rohan-almanac-main --message "Done."
115
+ ```
@@ -0,0 +1,59 @@
1
+ # Completion Audit
2
+
3
+ Date: 2026-06-27
4
+
5
+ This audit tracks the active goal: a clean, PR-ready Python package with a
6
+ Codex app-server target and binding-based Discord-to-Codex thread routing.
7
+
8
+ ## Verdict
9
+
10
+ Complete for the current package goal. The package is locally verified, the core
11
+ product shape is implemented, and the direct Codex wakeup path has live
12
+ `turn/start` proof against an idle rollout-backed Codex thread fork.
13
+
14
+ ## Requirement Audit
15
+
16
+ | Requirement | Current Evidence | Status |
17
+ | --- | --- | --- |
18
+ | Python package prepared for PyPI | `pyproject.toml`, `src/relayforge`, `src/relayforge/py.typed`, console script, auto guide `.pth`, example config in sdist, local config excluded from sdist, `python -m build`, `twine check dist/*` | Proven locally |
19
+ | Clean, small package boundaries | `core/`, `providers/discord/`, `targets/codex/`, `targets/codex_app_server/`, boundary tests | Proven by structure and tests |
20
+ | Provider-neutral core | Core uses string provider/target names and does not import provider or target implementations | Proven by `tests/test_boundaries.py` |
21
+ | Discord provider | REST read/send and Gateway listener exist under `providers/discord/` | Proven by unit tests and prior live Discord read/send/Gateway smoke |
22
+ | Binding-based routing | `Binding`, machine scoping, provider thread scoping, binding precedence, and fallback routing are implemented | Proven by router tests |
23
+ | Configurable channels, routes, and bindings | `relayforge init`, `channel-set`, `route-set`, `binding-set`, `channels`, `routes`, `bindings`, `check`, and config save/load support setup without hand-editing JSON | Proven by app and CLI tests, including the full local setup flow |
24
+ | Agent outbound replies | `relayforge binding-send` sends through the configured binding source or provider thread, using the source channel provider's sender | Proven by tests; live bound send remains optional evidence |
25
+ | Codex JSONL fallback | JSONL target and cursor-backed inbox reads exist | Proven by unit tests |
26
+ | Codex app-server target | Target initializes app-server, resumes an existing thread, and can start a turn with a notification envelope | Proven by fake-transport tests, live resume-only smoke, and live `turn/start` smoke |
27
+ | Per-binding Codex transport | `Binding.transport` selects a separate app-server stdio command per transport string | Proven by unit tests |
28
+ | Raw default agent guides | Wheel install writes managed Relayforge guidance blocks to `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` on Python startup | Proven by installed-wheel packaging tests, including opt-out |
29
+ | Steering docs current | Live agreement, ownership map, verification matrix, worklog, and next-agent brief describe the current system | Proven by direct inspection |
30
+ | Final live direct wakeup proof | Full `relayforge codex-smoke --binding <name>` against safe idle fork `019f069b-3ac6-7470-b502-4135061914c6` | Proven live |
31
+
32
+ ## Live Evidence
33
+
34
+ The final smoke used a temporary config created through public CLI setup
35
+ commands:
36
+
37
+ ```bash
38
+ relayforge init --force --config "$tmp_config"
39
+ relayforge channel-set --config "$tmp_config" --name smoke-discord --provider discord --id smoke-channel
40
+ relayforge binding-set --config "$tmp_config" --name smoke-codex-fork --source smoke-discord --target codex-app-server --codex-thread-id 019f069b-3ac6-7470-b502-4135061914c6 --cwd /Users/rohan/Desktop/Projects/almanac
41
+ relayforge check --config "$tmp_config"
42
+ relayforge codex-smoke --config "$tmp_config" --binding smoke-codex-fork --message "Relayforge live Codex app-server smoke test. Please reply briefly that the smoke test reached this fork."
43
+ ```
44
+
45
+ The command returned:
46
+
47
+ ```json
48
+ {"threadId": "019f069b-3ac6-7470-b502-4135061914c6", "method": "turn/start"}
49
+ ```
50
+
51
+ `codex_app.read_thread` then showed a new turn on the fork and the thread was
52
+ idle afterward.
53
+
54
+ ## Optional Future Evidence
55
+
56
+ 1. Run one live `relayforge binding-send --binding <name>` smoke against a safe
57
+ Discord channel/thread to prove bound outbound sends outside unit tests.
58
+ 2. Run a live human-authored Discord Gateway message through the listener to
59
+ prove non-bot message routing.
@@ -0,0 +1,57 @@
1
+ # Relayforge Design
2
+
3
+ Relayforge is a small message router. Chat providers produce inbound messages.
4
+ Agent targets receive normalized messages and may send outbound replies through
5
+ the same provider.
6
+
7
+ The initial implementation supports Discord Gateway events, a Codex JSONL
8
+ fallback inbox, and a Codex app-server target for existing Codex threads. JSONL
9
+ is deliberately simple because it works without a running Codex app-server.
10
+
11
+ Core provider and target names are strings. Concrete provider behavior lives
12
+ under `providers/`; concrete target behavior lives under `targets/`.
13
+
14
+ ## Boundaries
15
+
16
+ - Providers know how to read and send messages on one platform.
17
+ - Targets know how to deliver a normalized message to an agent system.
18
+ - The router owns channel-to-target configuration and binding selection.
19
+ - The core types do not mention Discord, Slack, WhatsApp, or Codex internals.
20
+
21
+ ## Message Flow
22
+
23
+ ```text
24
+ Discord Gateway MESSAGE_CREATE
25
+ -> Discord provider normalizes message
26
+ -> Router matches provider + channel id to a named channel
27
+ -> Router resolves the matching binding or fallback route
28
+ -> Codex app-server starts a turn with a Relayforge notification, or Codex
29
+ JSONL appends one event
30
+ ```
31
+
32
+ The Codex app-server prompt is an explicit notification envelope. It includes
33
+ the binding name, provider source, message ID, author, timestamp, and message
34
+ body. The prompt tells the agent that a new chat message arrived and that any
35
+ reply should go back through the relay binding.
36
+
37
+ If a binding target fails and a route exists for the same channel, the router
38
+ uses the route as fallback. If no fallback route exists, the direct target error
39
+ is preserved.
40
+
41
+ Outbound messages use the provider adapter directly:
42
+
43
+ ```text
44
+ relayforge send --channel <id> --message "..."
45
+ -> Discord provider posts to the channel
46
+ ```
47
+
48
+ Bound outbound messages resolve through the binding first:
49
+
50
+ ```text
51
+ relayforge binding-send --binding <name> --message "..."
52
+ -> Relay resolves the binding source channel or source thread
53
+ -> Discord provider posts to that destination
54
+ ```
55
+
56
+ Bindings can be updated through `relayforge binding-set`. This keeps setup
57
+ scriptable for agents while preserving JSON config as the source of truth.
@@ -0,0 +1,18 @@
1
+ # Idea Evolution
2
+
3
+ ## 2026-06-26
4
+
5
+ Old hypothesis: A TypeScript `relayforge` package is enough because Discord
6
+ support is easy through `discord.js`.
7
+
8
+ New hypothesis: The package should be Python and PyPI-ready because the desired
9
+ artifact is a reusable agent relay package, not a local Node script.
10
+
11
+ Evidence that forced the change: The user explicitly requested slow development,
12
+ good code, Python, and PyPI deployment, with channels as a first-class concern.
13
+
14
+ Code or product assumption affected: The initial TypeScript scaffold should be
15
+ replaced rather than ported in place.
16
+
17
+ Follow-up test: Build a Python package where Discord is isolated under
18
+ `providers/discord` and routing tests do not import Discord.
@@ -0,0 +1,46 @@
1
+ # Live Agreement
2
+
3
+ Relayforge is a Python package for routing chat messages between human
4
+ channels and agent targets. It will be published to PyPI.
5
+
6
+ The product model is channel-first. A channel is a routable conversation endpoint
7
+ owned by a provider such as Discord, Slack, or WhatsApp. A target is an agent
8
+ delivery mechanism such as a Codex inbox file or direct Codex app-server
9
+ delivery.
10
+
11
+ ## Current Contract
12
+
13
+ - Python is the implementation language.
14
+ - Packaging must be PyPI-ready.
15
+ - Discord is the first provider.
16
+ - Slack and WhatsApp are future providers, so core names must not mention
17
+ Discord unless the file is inside the Discord provider.
18
+ - Codex JSONL inbox is the fallback target because it works without a running
19
+ Codex app-server process.
20
+ - Codex app-server is the direct Codex target. It resumes a configured Codex
21
+ thread and starts a turn through JSON-RPC.
22
+ - Codex app-server bindings can override the stdio command with `transport`.
23
+ Different transport strings use separate initialized app-server processes.
24
+ - Channel IDs are configuration data. The bot token can be shared by many
25
+ channels.
26
+ - Channels are named objects. Routes refer to channel names, not raw provider
27
+ IDs, so a multi-founder setup remains readable.
28
+ - Bindings are machine-local delivery rules. A binding maps a named source
29
+ channel, and optionally a provider thread ID, to an agent target.
30
+ - Bindings are bidirectional at the product level. Inbound provider messages can
31
+ wake the bound agent thread, and the agent can explicitly send a message back
32
+ through the same binding.
33
+ - Binding replies use the source channel's provider sender. Discord is the first
34
+ registered sender, but the app service is not hardcoded to Discord-only
35
+ binding replies.
36
+ - Config setup is command-driven. Agents can create the config, upsert channels,
37
+ upsert JSONL fallback routes, and upsert bindings without hand-editing JSON.
38
+ - The relay must support both event-driven listening and one-shot read/send
39
+ commands.
40
+
41
+ ## Non-Goals For The First Package Slice
42
+
43
+ - No hosted service.
44
+ - No database.
45
+ - No direct Codex Desktop UI automation.
46
+ - No hard dependency on Almanac.