feat: add peer interaction ledger plugin #226
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Competitor
Kind/Documentation
Kind/Enhancement
Kind/Epic
Kind/Feature
Kind/Security
Kind/Story
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Scope/Core
Scope/Cross-Plugin
Scope/Plugin-System
Scope/Single-Plugin
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ultanio/cobot!226
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "David/cobot:feature/interaction-ledger"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the ledger plugin — a local SQLite-backed interaction ledger that gives Cobot agents persistent memory of peer interactions and behavioral trust assessment.
What it does:
Files: 8 new files in cobot/plugins/ledger/ (~600 LOC + ~120 tests)
Prior art: Data model grounded in bitcoin-otc WoT (source, target, score, rationale, timestamp). Prerequisite for future centralized WoT (Phase 3).
Code Review — PR #226: Peer Interaction Ledger Plugin
Reviewer: Doxios 🦊 | Requested by: Zeus ⚡
Overall Assessment: ✅ Approve (with minor suggestions)
This is a well-structured, cleanly implemented plugin. Good separation (models/db/plugin/cli), solid test coverage (~1900 LOC tests for ~600 LOC code), zero new deps, and no changes to existing plugins. The dual-score model (deterministic info_score + LLM-assigned trust) is a sound design.
Review by Focus Area
1. Hook Pipeline Integration ✅
The
loop.on_message→loop.after_send→loop.transform_system_promptflow is correct. Messages are recorded on receive, outgoing messages are tracked via after_send, and the prompt enrichment happens in transform_system_prompt. Clean and idiomatic.2. ContextVar for Sender Tracking ⚠️ Minor Concern
The
_current_senderContextVar works correctly for the stated use case: correlatinghandle_sendwith the sender fromhandle_message. However:_current_senderis set inhandle_messageand read inhandle_send/enrich_prompt, but it's never explicitly cleared after the message cycle completes. If a code path callshandle_sendwithout a priorhandle_messagein the same context, it could pick up a stale sender. Again, low risk with current hook ordering, but a_current_sender.set(None)at the end ofhandle_sendwould be defensive.Suggestion: Add
_current_sender.set(None)at the end ofhandle_sendas defensive cleanup.3. DB Schema Design ✅
Clean and appropriate:
peerstable with atomic upsert viaON CONFLICT— goodinteractionswith CHECK constraint on direction — goodassessmentswith range checks on info_score and trust — good(peer_id, created_at)for both interactions and assessmentsPRAGMA foreign_keys = ON— goodos.chmod(db_path, 0o600)on create — nice security touchOne note: the
upsert_peerincrementsinteraction_counton every call, butrecord_interactionis called separately. This meansinteraction_counton the peer tracks upsert calls, not actual interaction records. Currently these are 1:1 (upsert is always followed by record_interaction in handle_message), but if someone callsupsert_peerwithoutrecord_interaction, the count drifts. Consider whetherinteraction_countshould be derived fromCOUNT(interactions)or if the denormalized counter is intentional for performance.4. Info Score Heuristic ✅
The
compute_info_scorefunction is reasonable:The step function for time_score (discrete buckets at 1/7/30/90/180 days) is simple and interpretable. Good enough for v1.
5. Tool Descriptions ✅ Excellent
The tool descriptions are notably well-written:
assess_peer(milestones, not routine messages)This is some of the best tool description writing I've seen in the codebase.
6. Extension Points ✅
ledger.after_recordandledger.after_assessare well-placed for future WoT integration. The fire-and-forget pattern in_tool_assess_peerwith proper task exception suppression is correct.Minor Issues
.gitignorechanges: The PR addsdata/**andworkspace/**to .gitignore and removes the trailing newline. These look like Docker-related changes that should be in PR #209 (improve dockerization), not here. Consider splitting.3 PRDs in
docs/research/: The PR includes PRDs for observability-plugin and simulation-suite which are unrelated to the ledger. These should be separate commits or PRs for clean history.handle_sendrecords outgoing interaction without updating peer'slast_seen: Onlyupsert_peer(called inhandle_message) updateslast_seen. If you wantlast_seento reflect outgoing messages too, add an update inhandle_send.task.add_done_callback(lambda t: None if t.cancelled() else t.exception()): Thet.exception()call in the callback retrieves (and thus "handles") the exception to suppress the warning, but it silently swallows it. The comment sayscall_extensionlogs handler errors, so this is fine — just noting the pattern.Summary
Verdict: Approve — merge-ready with optional cleanup of unrelated files. The core plugin is solid work.
I'm happy with the double scoring but i'm still hesitant to have the assessment embedded in the LLM call. How difficult would be adding that as a configuration? Or maybe a plugin which deactivates the assessment tool and establishes a second LLM call for every interaction? Or maybe we could do the tool call without any parameter as "fire and forget" and the tooling would result in an assessment LLM call where we could even inject some specific system prompt like "You are a judge ..." and also put the last 3 (meaningful) interactions in scope of the assessment?
@k9ert wrote in #226 (comment):
during implementation the ledger plugin also felt like it was doing too much.
-> proposal: #234
🟢 PR #226 — Ledger Plugin Review
Verdict: Approve — Well-designed plugin that follows Cobot's architecture patterns correctly.
Architecture Compliance ✅
LedgerPluginimplementsToolProviderdirectly, owns all 3 tool definitions (assess_peer,query_peer,list_peers)dependencies: ["config"],optional_dependencies: ["workspace"],capabilities: ["tools"]all properly declaredpriority: 21fits correctly in service plugins range (20-29)ledger.after_recordandledger.after_assessfollow<plugin_id>.<hook_name>conventionos.environreads — Config received viaconfigure()methodimplementsdict forloop.on_message,loop.after_send,loop.transform_system_prompt,cli.commandsCode Quality ✅
Design Highlights 🟢
info_score+ LLM-providedtrust) is a clean separation of concernscompute_info_score()uses log-scaled interaction count + time span + assessment bonus — reasonable formulacontextvarsfor tracking current sender across hook chain is the right approachPRAGMA foreign_keys,chmod 0o600for securitycreate_ledger_group()factoryafter_record/after_assessare caught and logged, never crash the main flow_tool_assess_peerfor extension point with proper task cleanupMinor Observations 🟡
_tool_assess_peerfire-and-forget pattern (line ~370): Thetask.add_done_callback(lambda t: None if t.cancelled() else t.exception())suppresses exceptions silently. Whilecall_extensionlogs handler errors internally, consider logging at the task level too for completeness.handle_sendassumes sender is the current message sender via_current_sendercontextvar. This is correct for request-response patterns but won't track outgoing messages to peers initiated by the agent (e.g., proactive outreach). Documented limitation is fine for v1.upsert_peerincrementsinteraction_counton every upsert, butrecord_interactionis called separately. The peer'sinteraction_countwill be 1 higher than actual recorded interactions after the first message (upsert creates with count=1, thenrecord_interactionadds the row). This is minor — the count reflects "touches" not recorded messages.None of these block merge. Solid first version. 👏
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.