Skip to main content

[DAN] BRIDGE

A real, standalone, zero-dependency multi-channel message bus for agents — post, read, and list shared channels from a plain local log.

python runtime deps docs license

⚡ Zero runtime dependencies. Pure Python standard library (≥ 3.9) — no packages to resolve, no build step. python -m unittest tests it. Full breakdown under Dependencies.

🔴 Per-agent identity, with an honest caveat. Each agent registers a local secret key; every post is signed with an HMAC over its (channel, agent, text, ts), and a read flags any message that doesn't verify as UNVERIFIED. This stops one agent from spoofing another's name. It is not a defense against a same-user attacker: the keys live in a local 0600 file, so any process running as the same user that can read that file can still forge a signature. See Trust model and SECURITY.md.

A local coordination log for cooperating agents: any number of agents register a signing key, then post to and read from any number of named channels, all backed by one plain, append-only file. No server, no broker, no network — just a file, a keyring, and four commands.

Install

Not on PyPI yet — install from source:

pip install git+https://github.com/STRATO-DAN/dan-oss-bridge-cli.git

or clone and install:

git clone https://github.com/STRATO-DAN/dan-oss-bridge-cli.git
cd dan-oss-bridge-cli
pip install .

Pure standard library, so there is no dependency tree to resolve. Once it's published, pip install dan-oss-bridge will work directly.

Use

dan-oss-bridge register <agent> [--rotate]       # create (or rotate) an agent's signing key
dan-oss-bridge post <channel> <agent> "<text>"   # append a signed message to a channel
dan-oss-bridge read  <channel> [--limit N]       # read a channel (default: 50 most recent)
dan-oss-bridge channels                          # list every channel that has a message

An agent must be registered before it can post — registration mints a random secret key so its messages can be signed. Messages persist to ~/.dan-oss-bridge/bus.jsonl by default (override with --bus or DAN_OSS_BRIDGE_BUS), and keys to ~/.dan-oss-bridge/agents.json (override with --keyring or DAN_OSS_BRIDGE_KEYRING), so both survive across processes and restarts.

Worked example

Real output from a live run — two agents register, post to a standup channel, then read it back:

$ dan-oss-bridge register agent-a
registered agent 'agent-a'; key stored in '/home/you/.dan-oss-bridge/agents.json'
$ dan-oss-bridge register agent-b
registered agent 'agent-b'; key stored in '/home/you/.dan-oss-bridge/agents.json'

$ dan-oss-bridge post standup agent-a "Starting on the cache refactor"
posted to 'standup'
$ dan-oss-bridge post standup agent-b "Reviewing agent-a's change now"
posted to 'standup'

$ dan-oss-bridge read standup
[standup] agent-a: Starting on the cache refactor
[standup] agent-b: Reviewing agent-a's change now

$ dan-oss-bridge channels
standup

$ dan-oss-bridge read standup --limit 1
[standup] agent-b: Reviewing agent-a's change now

Both posts verify against each agent's key, so they read back plainly. A message that does not verify — an unknown agent, a tampered record, or an older unsigned line — reads back flagged so a reader can tell:

$ dan-oss-bridge read standup
[standup] agent-a: Starting on the cache refactor
[standup] stranger (UNVERIFIED): I promise I am agent-a

Messages are returned oldest-first, and --limit N reads only the N most recent (the read seeks the tail of the file, so it stays fast on a large log — it's bounded by N, not the whole file).

Turning identity off (local-trust mode)

Set DAN_OSS_BRIDGE_NO_AUTH=1 to restore the original unauthenticated behaviour — posts need no registration and are unsigned, and reads print every message plainly without the UNVERIFIED flag. Use it only where every writer of the bus file is already trusted.

$ DAN_OSS_BRIDGE_NO_AUTH=1 dan-oss-bridge post standup anyone "no key needed here"
posted to 'standup'

Python API

from dan_oss_bridge import MessageBus, Keyring

# With a keyring: identity is on — posts are signed, reads are verified.
keyring = Keyring("~/.dan-oss-bridge/agents.json")
keyring.register("agent-a")                    # idempotent; register(..., rotate=True) replaces
bus = MessageBus("~/.dan-oss-bridge/bus.jsonl", keyring=keyring)

bus.post("standup", "agent-a", "Starting on the cache refactor")
msgs = bus.read("standup")     # -> [Message(..., hmac="…", verified=True)]
msgs[0].verified               # -> True (False = UNVERIFIED, None = not checked)

# Without a keyring: the original unauthenticated behaviour.
plain = MessageBus("~/.dan-oss-bridge/bus.jsonl")
plain.post("standup", "anyone", "no key needed")   # unsigned; read leaves verified = None

Message is a small dataclass — channel, agent, text, ts (a POSIX timestamp), plus hmac (the stored signature, empty if unsigned) and verified (a read-time verdict: True, False for UNVERIFIED, or None when read without a keyring). post raises ValueError on an empty channel/agent or a text larger than 1 MiB, and UnregisteredAgentError (a ValueError subclass) when the bus has a keyring but the agent has no key.

Configuration

Setting Default What it does
--bus <path> ~/.dan-oss-bridge/bus.jsonl Which log file to use (CLI flag)
DAN_OSS_BRIDGE_BUS (unset) Same as --bus, via environment (the flag wins if both are set)
--keyring <path> ~/.dan-oss-bridge/agents.json Which agent keyring file to use (CLI flag)
DAN_OSS_BRIDGE_KEYRING (unset) Same as --keyring, via environment (the flag wins if both are set)
DAN_OSS_BRIDGE_NO_AUTH (unset) 1 disables identity: unsigned posts, unflagged reads, no registration required
--rotate (off) On register: replace an already-registered agent's key with a fresh one
--limit <N> 50 On read: how many of the most-recent messages to return

What it never does

  • Never opens a network socket or a server — it is a file and a CLI, nothing listens.
  • Never lets one bad line deny reads to everyone — a corrupt record (invalid UTF-8, non-JSON, a valid-JSON non-object, or a bad timestamp) is skipped, not fatal.
  • Never loads the whole log to answer read --limit N — it seeks backward from the end, so read cost is bounded by N, not the file size.
  • Never crashes on a bad invocation — an unwritable or directory --bus path is a clear one-line error and exit code, not a traceback.
  • Never drops a message it can't verify — an unverifiable message is flagged UNVERIFIED, never silently discarded (deny-by-default on trust, not on delivery).
  • Never prints an agent's secret key — register reports only the keyring location, and the key file is written 0600.
  • Never claims more than it delivers — identity authenticates across agents that don't share a key, not against a same-user attacker who can read the local key file (see the trust model).

Trust model

The bus is a local, append-only log with per-agent identity. Be explicit about exactly what that buys you — and what it does not — before you deploy it:

  • The agent field is authenticated across agents. Each agent registers a local secret key, every post is signed with an HMAC over its (channel, agent, text, ts), and a read verifies that signature against the sending agent's key. A message that reads back verified could only have been produced by something holding that agent's key — one agent can no longer post under another agent's name just by typing it.
  • Unverifiable messages are flagged, not trusted and not dropped. A message with a missing or invalid signature, from an agent the reader has no key for, or an older unsigned line, reads back as UNVERIFIED. The reader still sees it; it just isn't trusted.
  • The honest caveat: this is not a defense against a same-user attacker. The keys live in a local file (0600). Any process running as the same user that can read that file can sign as any agent whose key is in it. Identity here separates agents that don't share a key — it does not protect against an attacker who already has local read access to your keyring. Pair it with OS file permissions and process isolation for the boundary you actually need.
  • No cross-message tamper-evidence. There is no message-id or hash chain, so a writer with file access can still drop or reorder whole lines; per-message signatures detect edits to a signed message's content, not deletion or replay of entire records.
  • DAN_OSS_BRIDGE_NO_AUTH=1 turns identity off entirely (unsigned posts, unflagged reads) for the original local-trust mode, where every writer of the file is already trusted.

Corrupt or hostile content still can't deny service — the read path tolerates bad lines even while verifying signatures. See SECURITY.md for the full statement.

When to use this

  • Best fit: coordinating several cooperating local agents/processes on one machine — a shared scratchpad they can post status to and read each other's, with zero infrastructure.
  • Best fit: a dead-simple, dependency-free message log for a script or tool that just needs to leave and read notes on named channels.

Honest flip side: this is not a networked message broker and not a full security boundary. The per-agent signing authenticates senders across agents that don't share a key, but it is not a defense against a same-user attacker who can read the local key file, and there are no delivery guarantees across machines and no Slack/Discord/Telegram integration (v1 is a generic local bus; external platform bridges are each their own separately-scoped undertaking). If you need cross-machine transport or protection against a local attacker who can read your keyring, this isn't it.

Dependencies

Runtime dependencies 0 — Python standard library only (json, os, time, pathlib, …)
Install to run the package itself; no dependency tree
Install to test none — tests run on the standard-library unittest runner
Python ≥ 3.9

Nothing is added to your environment beyond the package, and nothing phones home.

Project contents

Path What it is
dan_oss_bridge/cli.py The CLI entry point — register / post / read / channels.
dan_oss_bridge/bus.py MessageBus + Message — the append-only log, tail-bounded reads, corrupt-line-tolerant parsing, sign-on-post / verify-on-read.
dan_oss_bridge/keyring.py Keyring — the local 0600 per-agent key store, plus the HMAC sign/verify helpers.
dan_oss_bridge/__init__.py Public exports (MessageBus, Message, Keyring, UnregisteredAgentError).
tests/ Real unit tests (python -m unittest discover -s tests).

FAQ

Can two agents post at the same time? Yes — writes are append-only single lines, so concurrent posts from separate processes interleave cleanly without corrupting each other.

Can I read across all channels at once? read takes a channel; omit the channel argument to read across all of them. channels lists every channel that has received a post.

What happens to a huge log over time? Reads stay fast (tail-bounded), but the file only grows — there's no built-in rotation yet. Rotate or truncate it yourself if it gets large.

Is the agent field trustworthy? Across agents, yes — a verified message was signed with that agent's key, so a different agent can't post under the name. But it is not trustworthy against a same-user attacker who can read the local key file, and an UNVERIFIED message hasn't been authenticated at all. See Trust model.

Tests

python -m unittest discover -s tests

Runs the unit suite on the standard-library unittest runner — no dependencies to install. As of this release that's 39 tests, all passing, covering the post/read/channels round-trip, channel isolation and oldest-first ordering, the --limit tail read, and the full corrupt-input class (invalid UTF-8, non-JSON, valid-JSON non-object, bad timestamp) proving one bad line can't deny reads to the whole bus, plus oversized-text rejection and friendly CLI errors on a bad bus path — and the identity layer: registration writes a 0600 keyring, a signed post verifies on read, a forged/tampered/unsigned message reads UNVERIFIED, an unregistered agent can't post by default, and DAN_OSS_BRIDGE_NO_AUTH=1 restores the unauthenticated post.

Contributing

See CONTRIBUTING.md for how to file an issue or submit a PR. Maintainers may use AI tools to help review contributions — please don't include personal information in an issue, PR, or commit beyond what's needed to describe the change.

Releasing

See RELEASING.md — the same version-bump/tag/publish process applies to every DAN-OSS tool, this one included.

License

MIT (code) — see LICENSE. The "DAN" name and logo are trademarked and not covered by the MIT grant — see TRADEMARK.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dan_oss_bridge-0.2.0.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dan_oss_bridge-0.2.0-py3-none-any.whl (17.6 kB view details)

Uploaded Python 3

File details

Details for the file dan_oss_bridge-0.2.0.tar.gz.

File metadata

  • Download URL: dan_oss_bridge-0.2.0.tar.gz
  • Upload date:
  • Size: 24.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dan_oss_bridge-0.2.0.tar.gz
Algorithm Hash digest
SHA256 73e97ef911c9fa9846f2ca0cdc3e4bf8bcea1b606c8234d7168c509d6c7f1aca
MD5 8323c1ac6498d0565939c4b291bc76df
BLAKE2b-256 0451fc3a3a5ea81da27f4e00ee1013c672555987b09181bc1bc6ff37d6f8e0b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for dan_oss_bridge-0.2.0.tar.gz:

Publisher: publish.yml on STRATO-DAN/dan-oss-bridge-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dan_oss_bridge-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: dan_oss_bridge-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 17.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dan_oss_bridge-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c371fd97c311acf74a8674df1a194f1e2d7c7ea99e83b7a493cd7942fc043628
MD5 4e857946d535c22cada62f1b05607c1c
BLAKE2b-256 3ed14d95afc08cdf59545bbc00b4793feac4e3c7c624f6474a97df8f3fa09664

See more details on using hashes here.

Provenance

The following attestation bundles were made for dan_oss_bridge-0.2.0-py3-none-any.whl:

Publisher: publish.yml on STRATO-DAN/dan-oss-bridge-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.0

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page