Skip to content

feat(a2a): expose inbound message metadata to sessions - #3

Open
RuniThomsen wants to merge 3 commits into
mainfrom
feat/a2a-inbound-metadata-fork-main
Open

RuniThomsen wants to merge 3 commits into
mainfrom
feat/a2a-inbound-metadata-fork-main

Conversation

@RuniThomsen

Copy link
Copy Markdown
Collaborator

Summary

  • advertise the optional https://runi.services/a2a/ext/task/v1 extension in the A2A Agent Card
  • render inbound Message.metadata generically beside message Parts in the receiving Hermes session
  • preserve the existing untrusted-peer framing and text-only audit/persistence boundary

Verification

  • TDD RED: 3 targeted tests failed before implementation
  • scripts/run_tests.sh tests/plugins/test_a2a_plugin.py tests/plugins/test_a2a_phase23.py — 154 passed
  • python -m pytest tests/plugins/test_a2a_plugin.py::TestInboundRoundTrip::test_live_server_passes_message_metadata_into_agent_session -m integration -q — 1 passed
  • git diff --check — passed

Scope

This does not interpret extension keys or add a Runi-specific adapter path. Any JSON-object Message.metadata is serialized next to the Parts and passes through the same inbound safety frame. Outbound metadata remains covered separately by NousResearch#90396.

RuniThomsen and others added 3 commits August 22, 2026 09:43
Reviewed once and verified 111/111. Makes the 900-second Hermes-seat reply deadline and matching orphan watchdog config-native.
Co-Authored-By: Claude <noreply@anthropic.com>
RuniThomsen pushed a commit that referenced this pull request Sep 24, 2026
… fingerprint never authorizes a signal

NousResearch#111617 review (andrexibiza P1 #3/#4, kvnloo nit):

- worker_started_at persisted only gateway.status.get_process_start_time(): on Linux that
  is /proc/<pid>/stat field 22, clock ticks since THIS boot. The threat is a row surviving
  a reboot, and that counter does not, so an unrelated process on a later boot with the
  same PID and the same tick value passed _start_times_agree(). The fingerprint is now
  "<gateway.drain_control.current_instantiation_epoch()>|<start>" (boot_id + PID-1 start,
  the witness the drain marker already uses); both halves must match. Integer values on
  rows written before this change keep the start-time-only comparison.
- A failed capture persisted NULL, which _pid_recycled treats as the legacy pre-fingerprint
  row and falls back to bare PID existence - a new spawn silently recreated the NousResearch#89614/
  NousResearch#99558 kill authority. A failed capture now persists UNVERIFIED_WORKER_FINGERPRINT: the
  claim is held while the PID is live (never released beside it, never SIGTERM/SIGKILLed
  by timeout, stale-claim, manual reclaim, archive or the terminal reaper) and reclaimed
  once it is gone. NULL stays legacy-only.
- Every tasks UPDATE that nulls worker_pid nulls worker_started_at too (archive_task and
  the reclaim/timeout/reopen paths): the fingerprint is part of the kill-authority tuple
  and must not outlive its pid.

Live (real sleeper child): reboot-shaped row (same pid, same tick, other boot id) ->
reclaimed to ready, child untouched; matching fingerprint -> SIGTERM delivered, exit -15.
tests/hermes_cli/test_kanban_worker_pid_fingerprint.py: +2 hostile tests, both red on base.

Not changed: the check-then-act window between _pid_recycled and kill (kvnloo P2) is
real but needs pidfd_open/pidfd_send_signal (Linux 5.3+) to close atomically; left as
the documented residual of "never kills a DETECTED recycled PID".
RuniThomsen pushed a commit that referenced this pull request Sep 24, 2026
Adopts the DNS-rebinding-pinned transport hardening (repo issues #2/#3,
PR #3) and the starter-feed/settings failure-surfacing + SSRF-gated icon
proxy fix (issue #6, PR #8). Full range in
tony-simons-aiowa/hermes-newswire deccdc4..e6b438e (13 commits):

Security-relevant highlights:
- All outbound fetches (feeds, redirects, icons) now go through a pinned
  transport: the SSRF gate's validated address set is bound to the actual
  connection — no second DNS lookup, so DNS rebinding/TOCTOU has no
  window; the plugin fails closed if the pin seam changes.
- New GET /icon.json proxies favicons through the same gate and returns
  base64 data URLs — the renderer's <img> no longer performs unpinned
  DNS resolutions of feed-controlled hostnames. 64 KB cap enforced
  mid-transfer; image content-type allowlist; bounded, normalized TTL
  cache.
- Renderer surfaces backend failures (settings/sources banners,
  starter-feed inline errors) instead of silent no-ops.

Capabilities unchanged (all empty — dashboard plugin, no tools/hooks/
env). Verification at the new pin: 129 pytest, 33 renderer interaction
checks, 26 ESM render smoke, hermes plugins validate clean.
RuniThomsen pushed a commit that referenced this pull request Sep 24, 2026
…rametrize

test_cmd_gc_negative_days_leaves_workspaces_untouched and
test_cmd_gc_retention_bounds[negative-refuses] killed the same mutant
(the _cmd_gc guard turned into `if False`) and differed only in which
fixture they asserted survived. Create the archived scratch workspace in
the parametrized body (small helper) and assert on it alongside the
event/log checks; drop the standalone test.

WHY: one invariant, one test. The ordering claim ("refuse before ANY
sweep" — the unconditional workspace sweep runs first in _cmd_gc) is
still proven by the negative case; the 0/positive cases now also confirm
that valid values let the workspace sweep run. The file goes from 6 to 5
test functions with no lost mutation coverage.

Finding: simplify/D.quality.md #3
(tests/hermes_cli/test_kanban_gc_retention.py:82-97).

Proof: with the _cmd_gc guard mutated to `if False`,
test_cmd_gc_retention_bounds[negative-refuses] fails (1 failed, 7
passed); head is green (8 passed).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant