feat: hermes agent autonomy — skill evolve + memory extract + agent configure + watcher - #111
Conversation
Feature A — Skill Auto-Evolution (internal/skill/evolve.go) - ScanEvolveCandidates: 掃描失敗率 >= 40%(min 10 invocations, 7d 冷卻期)的 skill - LLM(Haiku)生成改寫 proposal → skills/<name>/proposals/<ts>.md - ApproveProposal / RejectProposal:人工審核後套用,原版備份至 history/ - cron ID "skill_evolve" 觸發(daily 3am);cfg.SkillEvolve.Enabled 控制 opt-in Feature B — Deep Memory Extraction (internal/memory/extract.go) - reflection score >= 4 + cost >= $0.10 + success 觸發 - Haiku 萃取 0-3 條跨 session 知識 → memory/extract:<slug>.md - setMemoryWithCRUD:ADD/UPDATE/NOOP/CONFLICT 防幻覺寫入 - FIFO log → memory/auto-extracts.md(上限 100 筆) - cfg.DeepMemoryExtract.Enabled 控制 opt-in Also: fix http_review_test.go postReviewComment signature (context.Context) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
架構原則、agent configure、task watcher 的完整設計 spec。 PR #111 實作依照此文件進行,reviewer 可對照比較。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- internal/agent/configure.go: Configure() / ConfigureAll() — asks each agent
which capabilities it needs via claude -p, generates CLAUDE.md +
capabilities/{index,detail}.md files
- internal/agent/watcher.go: Watch() — polls taskboard for todo tasks with an
assignee and spawns `claude --cwd <agentDir> -p <task>` autonomously
- internal/cli/agent.go: adds `tetora agent configure <name>|--all` and
`tetora agent watch [--daemon] [--interval=]` subcommands; configure also
wires weekly-review cron jobs into jobs.json
- wire.go: fix expandPrompt memory regex to support hyphens and colons so
{{memory.auto-extracts}} and {{memory.extract:slug}} expand correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BuiltinCapabilities now has all 4 entries from spec 3.7 - Capability struct gets ConfigFlag field for config-only capabilities - ConfigureResult.ConfigFlags carries which keys to set (section.field) - applyConfigureResult writes config flags via MutateConfig - detail file deep-memory-extract.md explains what's enabled and how to subscribe Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brings in nlp/classify/handoff archival, health inline, and httputil/text fold from the slim-plan PRs to resolve PR conflict. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The diff is already provided in the conversation context. Writing the review based on the full diff analysis. PR #111 Code Review —
|
Critical:
- watcher: validate t.Assignee resolves under cfg.AgentsDir to block path
traversal payloads from DB-controlled assignee values
- extract: ValidateExtract now takes *Extract so the 600-char body cap
the caller observes the truncation; add regression test
High:
- watcher: add MaxConcurrent semaphore (default 4) so 50 queued tasks
don't spawn 50 claude processes simultaneously
- watcher: markTaskDoing now atomic via single UPDATE+SELECT changes()
call, dropping the lossy verification SELECT round-trip
- agent: replace [^{}] regex with depth-aware JSON object extractor that
tolerates nested braces in reason/example fields
- agent: 120s context timeout on askAgent so a hung claude doesn't
block tetora agent configure forever
Medium:
- memory: serialise AppendToAutoExtractsMD with sync.Mutex; multiple
goroutines from runDeepMemoryExtract can no longer corrupt the FIFO
- agent: warn on unknown capability IDs returned by the LLM
- agent: warn when SOUL.md is missing during configure
- skill: updateProposalStatus now returns error, propagated through
Approve/RejectProposal
- memory: implement DailyBudgetUSD enforcement via Reserve/Adjust API
in memory pkg; wire.go reserves before extract, reconciles after
- cron: extract "skill_evolve" magic string into SkillEvolveJobID const
Low:
- skill: nanosecond suffix on proposal IDs prevents same-second collision;
ListProposals parses the leading 19 chars as the timestamp
- memory: ParseExtractsJSON prefers array form when '[' precedes '{'
- agent: drop hand-rolled contains() helper from configure_test
- skill: drop unused mkSkillMD helper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
lessons.md 讀取完畢,開始對 PR #111 進行 code review。 PR #111 Code Review —
|
| Missing | Risk |
|---|---|
watcher.go (zero tests) |
Medium — optimistic lock logic is untested |
resolveAgentDir unit tests |
High — security function |
ParseExtractsJSON trailing-content case |
Low |
Verdict: Request Changes
Two high-severity correctness bugs (markTaskDoing multi-statement SQLite, and hardcoded absolute path in CLAUDE.md) must be fixed before merge. The silent rollback failure is also a reliability issue worth addressing. Everything else can be addressed in follow-up tickets. Architecture and overall design are sound; test coverage for the extract and evolve modules is above average.
Sync dashboard.html with dashboard/ sources to satisfy CI gate. The "(sequential, ~30–90s each)" progress hint from 3829495 was in dashboard/ but never compiled into dashboard.html. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Issues confirmed. Writing the review now. PR #111 Code Review —
|
Two related GitLab review fixes — both regressed multiple times because glab's CLI flag semantics differ from gh's. - postReviewComment: switch GitLab path from `--form body=@file` (multipart upload, treats @file as binary attachment) to `-F body=@file` (typed JSON field, reads @file as string contents). Per `glab api --help`, glab's -F is `--field` (the equivalent of gh's -f), and --form is multipart-only. This has flipped at least 4 times in history; lock down args via a new pure helper `reviewCommentCmdArgs` + TestReviewCommentCmdArgs that asserts -F is present and --form / -f are absent. - fetchReviewDiff GitLab branch: switch from CombinedOutput() to Output() with separate stderr buffer. glab prints non-fatal warnings (auth-token expiring, etc.) to stderr; CombinedOutput merged them into stdout, leading to `parse mr diffs: invalid character 'A' after top-level value` when the warning text was prepended to the JSON array. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…truncation
fetchPRContext was github-only and oldest-first with a 4KB total cap, so
when a PR had many comments the *latest* ones — which carry the current
state of discussion — were exactly what got dropped. Combined with zero
context for GitLab MRs, the review agent kept re-flagging issues that had
already been addressed.
Changes:
- Take last 2 reviews + last 2 comments (newest), output chronologically
so the LLM reads newest LAST.
- No per-item truncation. Soft per-item ceiling 32KB triggers middle-elision
(head 2/3 + marker + tail 1/3, rune-safe via boundary walk) so a runaway
comment never silently drops its tail (where the latest reply lives).
- Total soft cap raised to 80KB; emits log.Warn but does not drop newest.
- GitLab MR support: glab api projects/<path>/merge_requests/<iid> for
title/body, /notes?per_page=20&order_by=created_at&sort=desc for the
latest notes. Reverses to chronological before lastN. Filters system:true
notes ("added 1 commit", etc.) so they don't displace human comments.
- Pure formatPRContext(view) extracted; both hosts feed the same struct.
- validateReviewHost now runs on the GitLab path too (was a no-op before
because the function early-returned for non-github hosts).
- <pr_context source=...> tag now reflects actual host (github/gitlab)
instead of always saying "github".
Tests: TestFormatPRContext (last_two_only, chronological_order,
system_notes_filtered, description_does_not_displace_latest,
middle_elision, rune_boundary, empty) + TestElideMiddle (small_passthrough,
exact_boundary_passthrough, large_has_marker, rune_safe).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review — PR #111: hermes agent autonomyVerdict: Request Changes (posted as comment — cannot approve/request-changes on own PR) 🔴 Medium —
|
|
Review 已發出:#111 (comment) Review 摘要: Verdict: Request Changes 找到 3 個 medium 問題需要修復才能 merge:
Path traversal 在 |
…ope, rune safety, budget persistence) Medium: - memory: rune-safe Body truncation in ValidateExtract (was byte-slicing, could bisect CJK) - memory: persist daily budget to .daily-budget.json via InitDailyBudgetStore (sync.Once in wire.go) - agent: agentName validated via resolveAgentDir in Configure (same guard as watcher.go) High (3rd review): - watcher: release semaphore slot before !affected early return to prevent silent deadlock - skill: updateProposalStatus regex now scoped to frontmatter block only (not full file) Low: - watcher: wrap task description in <task-description> XML delimiters (prompt injection mitigation) - dispatch: runDeepMemoryExtract goroutine uses own context.WithTimeout, not shared reflCtx - cli/windows: buildDaemonCmd sets CREATE_NEW_PROCESS_GROUP so child survives parent exit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
PR 語言為英文,按 LRN-20260326-001 以英文撰寫 review。 PR #111 Code Review —
|
…atus, capability filter, path portability, atomic write Critical: - watcher: sanitize task ID (regex gate) and title (strip newlines) before prompt header; strip </task-description> from description to close XML-delimiter escape vector - skill: readProposalStatus now scans only frontmatter block (between --- delimiters) so body text "status: rejected" cannot misclassify a pending proposal Medium: - configure: filter unknown LLM-hallucinated capability IDs from ConfigureResult.Selected before returning so callers never see IDs with no files on disk - configure: writeCLAUDEMD writes filepath.Rel path instead of absolute ioProtocolPath so CLAUDE.md is portable across machines and workspace moves - memory: AppendToAutoExtractsMD uses temp-file + rename to prevent partial FIFO writes on SIGTERM Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
查到了關鍵資訊: PR #111 Code Review —
|
…r strip Critical: - watcher: markTaskDoing uses db.QueryArgs instead of fmt.Sprintf+db.Escape; rollback db.Exec in spawnAgent likewise converted to db.ExecArgs Medium: - watcher: title sanitization uses strings.Map to strip \r, \n, \t so Windows-style \r\n sequences cannot inject text before the XML delimiter Note: resolveAgentDir "duplication" flagged in review is a false positive — configure.go already calls resolveAgentDir from watcher.go (same package). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Port hermes-agent-self-evolution capabilities into Tetora as native Go. Three layers of implementation.
Layer 1 — Skill Auto-Evolution + Deep Memory Extraction
internal/skill/evolve.go: scans skills with failure rate ≥ 40% (min 10 invocations, 7-day cooldown), calls Haiku LLM to generate rewrite proposals →skills/<name>/proposals/<ts>.md. Human approves/rejects viatetora skill evolve approve|reject|list.internal/memory/extract.go: after reflection score ≥ 4 + cost ≥ $0.10 + success, Haiku extracts cross-session knowledge. CRUD-safe writes (ADD/UPDATE/NOOP/CONFLICT) tomemory/extract:<slug>.md+ FIFO log inmemory/auto-extracts.md(cap 100).~/.tetora/workspace/rules/tetora-agent-io-protocol.md: formal interface contract defining how agents subscribe to memory, read skill failures, and trigger self-improvement.Layer 2 — Agent Configure (
tetora agent configure)claude -p) which capabilities it wants, parses JSON response.~/.tetora/agents/<name>/CLAUDE.md(@SOUL.md+@capabilities/index.md+@io-protocol) andcapabilities/index.md(compact, always-in-context list).memory.auto-extracts,skill-evolve,weekly-review(adds cron job),deep-memory-extract(setsdeepMemoryExtract.enabledin config).Layer 3 — Task Watcher (
tetora agent watch)status=todo+ non-empty assignee.claude --cwd ~/.tetora/agents/<name>/ -p "[task]"— no SOUL.md injection, agent self-initializes via its own CLAUDE.md.doingbefore spawn, rolls back totodoif claude fails to start.--daemonflag detaches with PID file + log.Bug fix
expandPromptmemory regex now supports hyphens and colons:{{memory.auto-extracts}}and{{memory.extract:slug}}expand correctly.Architecture principle
Tetora = infrastructure (stores, provides). Agent = autonomous executor (decides what to read, when to act). Dispatch = pure routing
{task} → {agent}, no SOUL.md injection.Test plan
go test ./internal/skill/... ./internal/memory/... ./internal/agent/...— all passtetora agent configure <name>— generates CLAUDE.md + capabilities/ in agent dirtetora agent configure --all— runs for all registered agents, continues on failuretetora agent watch— spawns claude for assigned todo tasks{{memory.auto-extracts}}expands in task promptstetora skill evolve list|approve|rejectCLI works🤖 Generated with Claude Code