ReMe/plugins/auto-fin
jinliyl cae613d1c4
refactor(auto-fin): one note per topic, merged by a separate digest step (#558)
* fix(auto-fin): parse topic IDs from fenced JSON replies

* fix(auto-fin): limit report agent tool calls in prompt

* refactor(auto-fin): research news by topic before market open

* fix(config): update default model version for claude_code backend

- Change model version from qwen3.8-max to qwen3.7-plus
- Use environment variable LLM_MODEL_NAME to allow override
- Ensure backend configuration reflects updated model setting

* refactor(auto-fin): write one note per topic before the daily digest

The merge step did two jobs at once: it researched every topic and
combined the results into a single report. Split it the way daily-paper
separates analysis from its brief, so each topic earns a durable note of
its own.

- auto_fin_research_step writes one note per topic that had relevant
  news, tagged `kind: auto-fin-topic` and `topic` in frontmatter
- auto_fin_digest_step merges those notes into the day's brief with no
  tools of its own and appends a `## 主题详解` section linking back to
  each note
- a same-day rerun finds a topic's note by its `topic` frontmatter and
  replaces it in place, deleting the old file when the title changed
- base.py now owns the shared Markdown layer: title sanitizing, report
  normalizing, wikilink validation, note lookup, atomic frontmatter
  writes, and change tracking, so both steps share one write path
- the DingTalk step maps `auto_fin_digest_path` to `markdown_path`
  explicitly instead of relying on whichever step ran last
- drop the unused AutoFinTopicOutput schema and read `job_tools` from
  the step config rather than hardcoding `search`

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(dingtalk): surface rejection details when delivery fails

A failed group send only reported HTTPStatusError, so an operator had to
reproduce the request by hand to learn why DingTalk refused it. Include
the status code and the whitelisted error keys from the response body in
both the log line and the raised RuntimeError.

Only `code`, `message`, and `requestid` are reported: the request body
carries the message content and credentials, so an error response that
echoes it back must not reach the log. Detail is truncated to 200 chars.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(test): make the suite green on CI

- Point the cookbook claude_code model assertion at qwen3.7-plus, the
  default commit 9404e600 set, so the pre-existing red stops blocking
- Satisfy pylint on the auto-fin tests: prefer implicit booleaness for
  the recorded Agent calls and drop an unused tmp_path fixture

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(auto-fin): name notes after the topic, not the Agent title

The research Agent returned a whole paragraph as its title; that became a
filename and blew past the filesystem's 255-byte name limit, failing with
ENAMETOOLONG inside resolve_note_path. Topics are configured values, so
they are short and predictable - use them for file names and keep the
Agent title in frontmatter.

- Name topic notes after the topic and the digest after the run date
- Fold a byte budget into normalize_title as a safety net for long topics
- Take an AutoFinReportOutput in _write_report instead of loose fields
- Ask both prompts for a short title now that it is display-only

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: isolate per-topic research failures and scope frontmatter reads

A single failing topic used to fail the whole cron job and discard the news
already gathered for the topics that had not run yet -- the 09-19 09:24 run
lost its robot notes that way. Research now logs the failure, continues with
the remaining topics, and only fails the run when no topic produced a note.

frontmatter_read was the only frontmatter step without the _allowed_paths
check that read, write, edit, and frontmatter_update already honour, so an
Agent scoped to one file could still read another file's metadata.

- Isolate per-topic research failures and report them as failed_topics
- Fail loudly when every topic fails so an empty brief is never sent
- Apply _check_path_permission in FrontmatterReadStep

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(auto-fin): keep hand-edited notes and wikilink delimiters from breaking the run

Addresses three review findings on the topic-per-note rework.

- `find_note` and `read_note` now skip a note whose YAML frontmatter does
  not parse. A hand-edited note in the day directory raised
  `yaml.parser.ParserError`, which per-topic isolation surfaced as
  "Auto Fin research failed for every topic" and took the run down with it.
- `normalize_title` also strips `[`, `]` and `#`, which `WikilinkHandler`
  treats as target delimiters. `AI[算力]` used to emit a trailer link the
  parser could not read at all, and `C#` resolved to `.../C` plus an anchor.
- `_write_report` returns the body it actually wrote, and both callers
  propagate it, so the digest answer and the note handed to the digest Agent
  no longer carry links that validation had already downgraded on disk.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-20 14:52:56 +08:00
..
src/reme_auto_fin refactor(auto-fin): one note per topic, merged by a separate digest step (#558) 2026-09-20 14:52:56 +08:00
tests refactor(auto-fin): one note per topic, merged by a separate digest step (#558) 2026-09-20 14:52:56 +08:00
LICENSE feat: add DSH memory integration and organize extensions (#461) 2026-08-20 15:31:51 +08:00
pyproject.toml feat: auto-tag plugin-generated reports (#534) 2026-09-11 11:52:52 +08:00
README.md refactor(auto-fin): one note per topic, merged by a separate digest step (#558) 2026-09-20 14:52:56 +08:00
README_ZH.md refactor(auto-fin): one note per topic, merged by a separate digest step (#558) 2026-09-20 14:52:56 +08:00

Auto Fin Plugin

中文

Auto Fin fetches a rolling window of CLS telegraph news (24 hours by default), groups items by configured topics, researches each topic against ReMe history into its own note, and merges those notes into one dated brief whose validated wikilinks point back at every note. Current news and topic selection stay in runtime memory; only the notes and the brief become durable memory. This directory is an independent Python distribution. Its single reme.plugins entry point exposes a plugin.yaml containing the four Step backends and their Job configuration under application_defaults. Enable the installed plugin explicitly through plugins=["auto-fin"].

Auto Fin has no reliable market-price feed. It does not calculate returns, targets, or entry points and is not investment advice.

Quick start

1. Install ReMe and Auto Fin

python -m pip install "reme-ai[core]>=0.4.1.12"
reme plugins install plugins/auto-fin

2. Configure the model environment

Configure the LLM environment variables as described in the ReMe model-configuration guide. Other compatible models and providers can also be used.

3. Start ReMe with the plugin

reme start plugins='["auto-fin"]'

With no explicit config, ReMe loads default.yaml and adds the plugin to that service.

From another terminal, call the running HTTP service through ReMe's CLI client:

reme auto_fin topics="黄金,AI,存储芯片"

Or call its HTTP endpoint directly:

curl -s http://127.0.0.1:2333/auto_fin \
  -H 'Content-Type: application/json' \
  -d '{"topics":"黄金,AI,存储芯片"}'

The HTTP service also exposes the same Job as the auto_fin MCP tool at /mcp. The default topics are 黄金,机器人,半导体; an empty value also uses these defaults.

To host the application with both JSON and MCP access:

reme start plugins='["auto-fin"]' \
  service.backend=http

Custom application configs must provide agent_wrapper.default, a file_store.default with an enabled tag index, and the search, list_tags, frontmatter_read, and frontmatter_update Jobs used by Auto Fin and automatic tagging.

Pipeline

CLS public telegraph endpoint (rolling 24 hours)
        ↓
normalize and deduplicate in RuntimeContext
        ↓
topic Agent returns topic-to-news-ID mappings in prompt-sized batches
        ↓
one research Agent per topic examines its latest 20 articles, searches history up to three times,
and writes one note per topic with validated historical wikilinks
        ↓
digest Agent merges the notes into today's brief; code appends a link back to every note
        ↓
generate memory tags; the background file watcher refreshes indexes

auto_fin_data_step signs and paginates the same endpoint used by the CLS website. It starts at the decision time and stops only after covering the exact preceding 24 hours. Requests are rate-limited and retried; malformed records and records outside the window are discarded.

auto_fin_topic_step batches current news under a 100,000-character full-prompt limit and returns related news_id values for each topic. Code ignores unknown IDs and deduplicates repeated IDs, then preserves source-news order. One article may belong to multiple topics. If nothing is relevant, the job succeeds as a skip without writing a report.

auto_fin_research_step researches each nonempty topic with its latest 20 articles and writes one note per topic. It exposes only search, enforces a three-call search budget per topic, and keeps current CLS IDs, times, and titles as plain evidence. The prompt limits wikilinks to historical Markdown actually used by the Agent; the code-level boundary independently keeps only existing, workspace-relative Markdown targets. Missing, absolute, escaping, backslash, and self-referential targets are degraded to their readable aliases.

auto_fin_digest_step merges those notes into today's brief with no tools of its own: the notes already carry the research. Code appends a ## 主题详解 section linking back to every note, refreshes the daily index, and publishes the brief path for downstream steps. Both steps stop early when the topic step found no relevant news.

Same-day reruns reuse the note a topic already produced, feeding its body back to the research Agent and replacing it in place; the brief is replaced the same way. Writes are atomic. The workflow then runs auto_tag_step to update the generated files' memory-tag frontmatter; the normal background file watcher observes those source-file changes and refreshes derived indexes. No JSONL, intermediate Markdown, or structured Agent output is written.

Parameters

Parameter Default Purpose
date "" Empty uses today in Shanghai; an explicit value must equal today
now "" Optional ISO 8601 decision time for testing or replay
topics "黄金,机器人,半导体" Comma-separated topics; empty also uses these defaults
window_hours 24 Rolling number of hours of CLS telegraph news to fetch; must be positive
request_interval 10 Minimum delay in seconds after every CLS request attempt; may be zero
max_retries 3 Maximum attempts for each CLS page request; must be at least one

The plugin cron Job starts with the application and runs daily at 09:00 in the application timezone, which defaults to Asia/Shanghai. The rolling window uses timestamps and may cross calendar days. Report completion depends on news volume and model latency.

Output

.reme/daily/YYYY-MM-DD/<topic>.md                # one per topic that had relevant news
.reme/daily/YYYY-MM-DD/主题新闻观察(YYYY-MM-DD).md  # today's merged brief, linking back to every note

File names come from the configured topics and the run date rather than the Agent's title: an Agent title is free text and can outgrow the filesystem limit for one name component. Names are sanitized, byte-truncated, and disambiguated; the Agent title is kept in the title frontmatter field. Every file carries kind frontmatter (auto-fin-topic or auto-fin-digest), so a rerun finds and replaces the notes it produced instead of duplicating them. Each file includes a title, description, current CLS evidence, historical analysis, contextual wikilinks, and a fixed non-investment disclaimer; the brief ends with a ## 主题详解 list linking to the topic notes. A fetch failure fails the run explicitly, and so does a run in which every topic fails; a single failing topic only logs a warning and the remaining topics continue. No relevant current news is a successful skip.

Validation

python -m pytest plugins/auto-fin -v

Unit tests mock the CLS and Agent boundaries and do not contact external services.