Skip to content

feat(achievements): add export endpoint and agent summary (#18472) - #58015

Open
rlaope wants to merge 5 commits into
NousResearch:mainfrom
rlaope:feat/achievements-export-agent-summary
Open

feat(achievements): add export endpoint and agent summary (#18472)#58015
rlaope wants to merge 5 commits into
NousResearch:mainfrom
rlaope:feat/achievements-export-agent-summary

Conversation

@rlaope

@rlaope rlaope commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Implements the export & agent-communication workstream from the achievements upgrades tracking issue (#18472, spec in closed PR #18151). Achievements are currently trapped in state.json and the dashboard tab; this adds the two surfaces the spec defines for getting them out:

  • GET /api/plugins/hermes-achievements/export?format=json|markdown|svg&state=... — renders the current snapshot as machine-readable JSON, a README-pasteable Markdown badge table (shields.io badges + ██░░ progress bars, unlocked badges by default), or an SVG badge sheet with tier-colored markers. Unknown formats return a 400 JSON error instead of guessing.
  • GET /api/plugins/hermes-achievements/achievements/summary — the compact agent profile from the spec (strengths, gaps, top_tier, unlocked_ids, unlocked_count, total_count, session/tool totals), small enough for context injection.
  • agent_summary.json — the same payload is written next to state.json after every finished scan (best-effort, never fails the scan) and removed on /reset-state, so agents and external tools can read the profile without the dashboard running.

Scope notes, per the issue's guidance that the four workstreams land as separate PRs:

  • filter_and_sort_achievements() is added as the minimal state-filter seam the export formatters need; the filtering/sorting workstream can extend the same function with category/sort_by/limit without rework.
  • The hermes achievements CLI and TUI panel workstream is intentionally not included here; the CLI's export path can delegate to these formatters when it lands.

Two small deviations from the spec's reference snippets, both deliberate:

FastAPI response classes are stubbed in the existing no-FastAPI fallback so the plugin's dependency-free unit tests keep working.

(Supersedes #58012 — same change, reopened from my main account.)

Related Issue

Addresses the export/agent-summary section of #18472 (spec: #18151).

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • plugins/hermes-achievements/dashboard/plugin_api.py: add TIER_ORDER, agent_summary_path(), filter_and_sort_achievements(), export_json(), export_markdown(), export_svg(), _build_agent_summary(), _write_agent_summary(); new /export and /achievements/summary routes; write agent_summary.json in _run_scan_and_update_cache(); clear it in /reset-state; stub JSONResponse/PlainTextResponse in the FastAPI fallback.
  • plugins/hermes-achievements/tests/test_export_and_summary.py: 13 new unit tests (state filtering, JSON structure/filter, Markdown header/categories/badges/bad-timestamp handling, SVG rows/escaping/empty sheet, agent summary strengths/gaps/top-tier/empty data, agent_summary.json persistence).
  • plugins/hermes-achievements/README.md: document the new endpoints and the agent_summary.json artifact.

How to Test

  1. cd plugins/hermes-achievements && python3 -m unittest discover -s tests -v — 21 tests (13 new + 8 existing engine tests) pass without dashboard dependencies.
  2. Start the Hermes dashboard, open the Achievements tab once so a scan completes, then:
    • curl 'localhost:<port>/api/plugins/hermes-achievements/export?format=markdown' → Markdown badge table
    • curl 'localhost:<port>/api/plugins/hermes-achievements/export?format=svg' > badges.svg → renderable badge sheet
    • curl 'localhost:<port>/api/plugins/hermes-achievements/achievements/summary' → compact profile
  3. Check $HERMES_HOME/plugins/hermes-achievements/agent_summary.json exists after the scan and disappears after POST /reset-state.

…ch#18472)

Implement the export & agent-communication workstream from the
achievements upgrades spec (issue NousResearch#18472, spec PR NousResearch#18151):

- GET /export?format=json|markdown|svg&state=... renders the current
  snapshot as machine-readable JSON, a README-pasteable Markdown badge
  table (shields.io badges + progress bars, unlocked-only by default),
  or an SVG badge sheet with tier-colored markers. Unknown formats
  return a 400 JSON error.
- GET /achievements/summary returns the compact agent profile
  (strengths, gaps, top tier, unlocked ids, session/tool totals).
- agent_summary.json is written next to state.json after every finished
  scan (best-effort) and removed on /reset-state, so agents and external
  tools can read the profile without the dashboard running.
- filter_and_sort_achievements() is the minimal state-filter seam that
  the filtering/sorting workstream in NousResearch#18472 can extend with category,
  sort, and limit parameters.

SVG output escapes badge names/tiers to keep markup out of rendered
sheets. The agent summary path uses get_hermes_home() rather than a
hardcoded ~/.hermes so Windows-aware homes keep working. FastAPI
response classes are stubbed in the no-FastAPI fallback so the plugin
unit tests keep running without dashboard dependencies.

Tested: python3 -m unittest discover -s tests (21 tests: 13 new for
filtering, JSON/Markdown/SVG formatters, escaping, agent summary
builder, and agent_summary.json persistence; 8 existing engine tests
unchanged), plus an async endpoint smoke over stubbed snapshot data.
@alt-glitch alt-glitch added type/feature New feature or request comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have labels Jul 4, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing the export/agent-summary thread as a focused change to the existing achievements plugin.

Problems

  • The PR updates plugins/hermes-achievements/README.md, but the canonical built-in-plugin page has its own API and state-file tables at website/docs/user-guide/features/built-in-plugins.md:257-274. Those tables would omit both new routes and agent_summary.json.
  • plugins/hermes-achievements/tests/test_export_and_summary.py tests the rendering helpers and direct persistence only; it does not call export_achievements() or achievements_summary(). The route-level 400 contract and the Markdown/SVG media types therefore lack coverage.

Suggested changes

  • Update the built-in-plugin documentation table alongside the plugin README.
  • Add endpoint tests with a stubbed evaluate_all() covering JSON, Markdown, SVG, summary, and an unsupported format.

This is an automated hermes-sweeper review.

```text
GET /achievements
GET /achievements/summary
GET /export?format=json|markdown|svg&state=unlocked|discovered|secret

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update website/docs/user-guide/features/built-in-plugins.md too. Its canonical achievements API/state-file tables currently end at /reset-state and scan_checkpoint.json, so this route and agent_summary.json would be undocumented there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the canonical built-in plugin page in commit 959e62d. The achievements API table now documents GET /achievements/summary and GET /export with its format/state parameters, and the state-file table includes agent_summary.json.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
Signed-off-by: rlaope <105429536+rlaope@users.noreply.github.com>

@GottZ GottZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Two PRs address the same achievements export and agent-communication workstream: both add JSON/Markdown/SVG export, a compact agent summary endpoint, and persisted agent_summary.json; #58015 additionally updates the canonical built-in-plugin documentation.

Related pull requests

  • #58012 [closed] duplicate — (+405/-0) — superseded duplicate: Implements the reported export and agent-summary surfaces with helper and persistence tests, but it was closed so the work could be reopened from the author's main account as #58015; it remains relevant as the source-equivalent predecessor.
  • #58015 related — (+408/-0) — keep open with a salvage path: The diff implements the export routes, summary generation, persisted agent_summary.json, reset cleanup, rendering tests, and the canonical documentation update requested by the keep_open review. That review's remaining concern is still valid because the tests do not invoke export_achievements() or achievements_summary(), leaving response status, payload, and media-type contracts uncovered.

Duplicates

#58012 and #58015 contain substantially the same implementation; #58012 is the closed predecessor superseded by #58015, whose additional change updates website/docs/user-guide/features/built-in-plugins.md.

Suggested consolidation

Keep #58015 open with a salvage path: retain its focused export, summary, persistence, tests, and canonical documentation changes, then add endpoint-level tests with evaluate_all() stubbed for JSON, Markdown, SVG, summary, and unsupported-format behavior, as required by the visible keep_open review. Treat the already-closed #58012 as a duplicate superseded by #58015; no merge recommendation is supported by the recorded verification.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    subgraph Dup58012 ["PRs duplicating each other"]
        P58012["PR #58012 (closed)"]
        P58015["PR #58015 (open)"]
    end
    class P58012 closed
    class P58015 open
    class P58015 target
    click P58012 "https://github.com/NousResearch/hermes-agent/pull/58012"
    click P58015 "https://github.com/NousResearch/hermes-agent/pull/58015"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 41 kB of PR diffs, 8 kB of issue/PR text, 1 kB of discussion (2 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

…evel

The existing tests exercised the rendering helpers and persistence only, so
the route contracts were uncovered: the 400 response for an unsupported
format and the text/markdown and image/svg+xml media types could regress
silently. Drive export_achievements() and achievements_summary() directly
with evaluate_all() stubbed, asserting status codes, media types, and
payloads for JSON, Markdown, SVG, summary, and an unsupported format.
@rlaope

rlaope commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Added the requested endpoint-level coverage in da4080c5b.

The previous tests exercised the rendering helpers and persistence only, so the route contracts were uncovered. The new ExportEndpointTests drives export_achievements() and achievements_summary() directly with evaluate_all() stubbed:

  • JSONstatus_code == 200, payload counts round-trip
  • Markdownmedia_type == "text/markdown", rendered body content
  • SVGmedia_type == "image/svg+xml", body contains <svg
  • Unsupported format (pdf) — status_code == 400 and supported == ["json", "markdown", "svg"]
  • Summary/achievements/summary returns the agent summary payload
  • plus format case-insensitivity (MarkDown), which the dispatch lowercases

Verification: 17 passed in plugins/hermes-achievements/tests/test_export_and_summary.py (11 existing + 6 new), ruff check clean. I also mutation-checked the new tests — flipping status_code=400 to 200 and the Markdown media type to text/plain fails 3 of them, so they are not vacuous.

The canonical built-in-plugin doc table at website/docs/user-guide/features/built-in-plugins.md was already updated earlier in this PR, so both points from the review are now addressed.

rlaope added 2 commits August 4, 2026 08:40
Bare Path.read_text()/write_text() fall back to locale.getpreferredencoding()
— cp1252/cp936 on Windows — so the UTF-8 agent_summary.json artifact writes
mojibake or raises UnicodeDecodeError there. Pin encoding="utf-8" on the
persist path and its test read, per the NousResearch#71014 read_text campaign.
@rlaope

rlaope commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: rebased onto current main and fixed a Windows footguns (blocking) failure in 7ddc86e84.

The checker gained the read_text()/write_text() without encoding= rule (the #71014 campaign) after this PR's last green run, so the fresh run surfaced it. Two lines in this PR were affected — CI's log tail showed only the test one, but the checker flags both:

  • dashboard/plugin_api.py:1038_write_agent_summary() persisting agent_summary.json. This is the one that actually matters: on Windows it would write mojibake or raise UnicodeDecodeError via locale.getpreferredencoding() (cp1252/cp936).
  • tests/test_export_and_summary.py:171 — the matching read in AgentSummaryFileTests.

Both now pin encoding="utf-8". Left the pre-existing bare calls elsewhere in plugin_api.py alone to keep this diff scoped — happy to fold them in if you'd rather have the file done in one pass.

Verification: python scripts/check-windows-footguns.py --all clean (913 files), 28 plugin tests pass, ruff check clean. All required checks are green.

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

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants