Skip to content

Implement AGENT-002 Governance Analyst as running TypeScript code#15

Open
glandua wants to merge 1 commit intoregen-network:mainfrom
glandua:claude/agent-002-governance-sZ7EX
Open

Implement AGENT-002 Governance Analyst as running TypeScript code#15
glandua wants to merge 1 commit intoregen-network:mainfrom
glandua:claude/agent-002-governance-sZ7EX

Conversation

@glandua
Copy link
Contributor

@glandua glandua commented Feb 13, 2026

First working agent in the Regen Agentic Tokenomics framework. Implements all 3 governance analyst workflows from Phase 2.2 specs:

  • WF-GA-01: Proposal Analysis & Summarization
  • WF-GA-02: Voting Outcome Prediction & Alerts
  • WF-GA-03: Post-Vote Analysis & Reporting

Architecture:

  • Direct Cosmos LCD client (no MCP dependency)
  • OODA loop engine matching Phase 2.4 spec
  • Claude-powered analysis (Orient + Decide phases only)
  • SQLite local state (swappable to PostgreSQL)
  • Console + Discord webhook output
  • Layer 1 only: read-only, informational, no on-chain actions

Standalone Node.js — no ElizaOS dependency. Can be wrapped as ElizaOS plugin later without changing core logic.

Typechecks clean. Zero runtime dependencies beyond @anthropic-ai/sdk and better-sqlite3.

https://claude.ai/code/session_01HApBbQuEP529cmiPijDsaU

First working agent in the Regen Agentic Tokenomics framework.
Implements all 3 governance analyst workflows from Phase 2.2 specs:

- WF-GA-01: Proposal Analysis & Summarization
- WF-GA-02: Voting Outcome Prediction & Alerts
- WF-GA-03: Post-Vote Analysis & Reporting

Architecture:
- Direct Cosmos LCD client (no MCP dependency)
- OODA loop engine matching Phase 2.4 spec
- Claude-powered analysis (Orient + Decide phases only)
- SQLite local state (swappable to PostgreSQL)
- Console + Discord webhook output
- Layer 1 only: read-only, informational, no on-chain actions

Standalone Node.js — no ElizaOS dependency. Can be wrapped as
ElizaOS plugin later without changing core logic.

Typechecks clean. Zero runtime dependencies beyond @anthropic-ai/sdk
and better-sqlite3.

https://claude.ai/code/session_01HApBbQuEP529cmiPijDsaU
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @glandua, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers the initial implementation of AGENT-002, a governance analyst agent for the Regen Network. The agent automates the analysis of governance proposals, monitors voting progress, and generates post-vote reports, providing critical informational insights. It leverages an OODA loop architecture and integrates with Claude AI for sophisticated data interpretation, operating as a read-only Layer 1 component to enhance transparency and stakeholder awareness within the network.

Highlights

  • New Governance Analyst Agent (AGENT-002): Introduced AGENT-002, the first working agent in the Regen Agentic Tokenomics framework, designed to provide read-only governance intelligence for the Regen Network.
  • Three Core Workflows Implemented: Implemented all three governance analyst workflows from Phase 2.2 specifications: Proposal Analysis & Summarization (WF-GA-01), Voting Outcome Prediction & Alerts (WF-GA-02), and Post-Vote Analysis & Reporting (WF-GA-03).
  • OODA Loop Architecture: The agent is built around an OODA (Observe, Orient, Decide, Act) loop engine, aligning with Phase 2.4 specifications for agentic behavior.
  • Claude-Powered AI Analysis: Utilizes Anthropic's Claude AI for the 'Orient' and 'Decide' phases of the OODA loop, enabling intelligent analysis and summarization of governance data.
  • Standalone Node.js Application: Developed as a standalone Node.js application, with direct Cosmos LCD client integration (no MCP dependency) and local SQLite for state persistence, ensuring flexibility and ease of deployment.
Changelog
  • agent-002-governance-analyst/.env.example
    • Added example environment variables for configuration, including Anthropic API key, Regen LCD URL, Discord webhook, polling interval, and Claude model.
  • agent-002-governance-analyst/.gitignore
    • Added common build artifacts, dependency directories, database files, and environment variables to be ignored by Git.
  • agent-002-governance-analyst/README.md
    • Added comprehensive documentation detailing the agent's purpose, workflows, architecture, quick start guide, configuration, framework mapping, design decisions, and governance layer.
  • agent-002-governance-analyst/package-lock.json
    • Added a lockfile to ensure consistent dependency installations across environments.
  • agent-002-governance-analyst/package.json
    • Added project metadata, scripts for building, running, and developing the agent, and defined core dependencies (Anthropic SDK, better-sqlite3) and development dependencies (TypeScript, tsx).
  • agent-002-governance-analyst/src/analyst.ts
    • Added core logic for AI-powered analysis using Anthropic's Claude, including functions for proposal analysis, voting status assessment, and post-vote report generation, along with a detailed system prompt for the agent's persona.
  • agent-002-governance-analyst/src/config.ts
    • Added a centralized configuration file for the agent, managing API keys, URLs, polling intervals, and governance parameters, including a validation function.
  • agent-002-governance-analyst/src/index.ts
    • Added the main entry point for the agent, including a startup banner, configuration validation, Ledger client initialization, and the execution loop for the three governance workflows, supporting both single-run and continuous polling modes with graceful shutdown.
  • agent-002-governance-analyst/src/ledger.ts
    • Added a client for interacting with the Regen Ledger's Cosmos LCD (REST) API, providing methods to fetch governance proposals, tallies, votes, and staking pool information.
  • agent-002-governance-analyst/src/ooda.ts
    • Added a generic OODA loop executor, defining an interface for workflows and handling the lifecycle, logging, and persistence of workflow executions.
  • agent-002-governance-analyst/src/output.ts
    • Added an output dispatcher to send agent messages to the console and optionally to a Discord webhook, with message formatting and truncation logic.
  • agent-002-governance-analyst/src/store.ts
    • Added a local SQLite database store for persisting agent state, including workflow execution records, proposal analyses, voting snapshots, and post-vote reports, with schema migration.
  • agent-002-governance-analyst/src/types.ts
    • Added TypeScript interfaces for Cosmos SDK/Regen Ledger governance data, OODA loop execution, workflow-specific data structures, and output messages.
  • agent-002-governance-analyst/src/workflows/post-vote-report.ts
    • Added the implementation for WF-GA-03, handling the observation of finalized proposals, orientation to identify unreported ones, decision to generate reports via Claude, and acting by saving and outputting these reports.
  • agent-002-governance-analyst/src/workflows/proposal-analysis.ts
    • Added the implementation for WF-GA-01, managing the observation of new proposals, orientation to identify unanalyzed ones, decision to generate analyses via Claude, and acting by saving and outputting the analyses.
  • agent-002-governance-analyst/src/workflows/voting-monitor.ts
    • Added the implementation for WF-GA-02, responsible for observing active voting proposals, orienting by calculating turnout and update frequency, deciding on outcome projections and alert levels via Claude, and acting by saving snapshots and sending alerts.
  • agent-002-governance-analyst/tsconfig.json
    • Added TypeScript configuration for the project, specifying compiler options, included files, and excluded directories.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the AGENT-002 Governance Analyst, a well-architected, standalone TypeScript agent designed to monitor and analyze Regen Network governance proposals using the Anthropic Claude LLM. It follows the OODA loop pattern and uses a local SQLite store for state. A medium-severity vulnerability related to Indirect Prompt Injection was identified, where untrusted data from blockchain proposals is directly embedded into LLM prompts, potentially allowing an attacker to manipulate the agent's output and broadcast malicious content to Discord. Remediation involves using structured prompts with clear delimiters for untrusted data. Furthermore, a critical bug related to the handling of bondedTokens affects all three workflows, causing incorrect data to be used for analysis. Suggestions are also provided to improve maintainability and correctness by addressing hardcoded values in the LLM system prompt, magic numbers for proposal statuses, and an invalid default model name in the configuration.

Comment on lines +129 to +134
const report = await generatePostVoteReport(
proposal,
tally,
"0",
simpleVotes
);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The generatePostVoteReport function is called with a hardcoded "0" for bondedTokens. This will cause the turnout calculation within the function to be incorrect (likely resulting in 0%), leading to a flawed post-vote analysis.

The bondedTokens value is fetched correctly in the observe phase but is not propagated to the decide phase. You should pass this value through the orient phase to make it available here.

Suggested change
const report = await generatePostVoteReport(
proposal,
tally,
"0",
simpleVotes
);
const report = await generatePostVoteReport(
proposal,
tally,
orientation.bondedTokens,
simpleVotes
);

Comment on lines +123 to +127
const analysis = await analyzeProposal(
proposal,
tally,
"0" // bondedTokens passed separately
);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The analyzeProposal function is called with a hardcoded "0" for the bondedTokens parameter. This will provide incorrect information to the LLM, as the prompt for analysis includes the Bonded tokens value.

The bondedTokens value is correctly fetched in the observe phase but is not passed down to the decide phase. This value should be passed through the orient phase to be used here.

Suggested change
const analysis = await analyzeProposal(
proposal,
tally,
"0" // bondedTokens passed separately
);
const analysis = await analyzeProposal(
proposal,
tally,
orientation.bondedTokens
);

Comment on lines +150 to +155
const statusReport = await assessVotingStatus(
item.proposal,
item.tally,
"0", // passed via tally context
item.previousSnapshot
);
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The assessVotingStatus function is called with a hardcoded "0" for bondedTokens. This is incorrect and will lead to an inaccurate turnout calculation within assessVotingStatus, as well as providing wrong information in the LLM prompt.

The bondedTokens value is fetched in the observe phase and should be passed down through the orient phase to be available here.

Suggested change
const statusReport = await assessVotingStatus(
item.proposal,
item.tally,
"0", // passed via tally context
item.previousSnapshot
);
const statusReport = await assessVotingStatus(
item.proposal,
item.tally,
item.bondedTokens,
item.previousSnapshot
);

Comment on lines +22 to +27
Regen Network Context:
- Cosmos SDK-based blockchain for ecological assets (eco-credits)
- ~224M REGEN total supply, ~3.2M in community pool
- 75 active validators, 33.4% quorum, 50% pass threshold
- 61+ historical governance proposals
- Core modules: x/ecocredit, x/gov, x/staking, x/marketplace
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The SYSTEM_PROMPT contains several hardcoded values (total supply, community pool size, validator count, proposal count) that will become stale over time. Providing outdated context to the LLM can lead to inaccurate analyses. These values should be dynamic.

Consider converting the SYSTEM_PROMPT into a function that accepts an object with up-to-date chain state, which can be fetched during the observe phase of the workflows. This ensures the agent always operates with the most current information.

tally: TallyResult,
bondedTokens: string
): Promise<string> {
const prompt = buildAnalysisPrompt(proposal, tally, bondedTokens);
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The agent constructs prompts for the Anthropic Claude LLM by directly concatenating untrusted data from blockchain governance proposals (e.g., proposal.content.description, proposal.content.title). Since anyone can submit a proposal to the Regen Network blockchain, an attacker can craft a malicious proposal containing instructions designed to override the agent's system prompt (Indirect Prompt Injection).

Because the agent is fully automated and its analysis is automatically posted to a Discord webhook (in src/output.ts), a successful prompt injection could allow an attacker to broadcast arbitrary messages, phishing links, or misinformation to the community Discord channel, appearing as if they were generated by the trusted analyst agent.

To remediate this, use clear delimiters (e.g., XML tags like <proposal_description>...</proposal_description>) to wrap untrusted input and instruct the LLM in the system prompt to treat content within those tags as data only, not instructions. Additionally, consider implementing a secondary check or sanitization step for the LLM's output before it is posted to external channels.

Comment on lines +79 to +110
const prompt = `Provide a voting status update for this Regen Network governance proposal.

## Proposal
- ID: #${proposal.id}
- Title: ${proposal.content.title}
- Type: ${proposal.content["@type"]}
- Status: ${proposal.status}
- Voting ends: ${proposal.voting_end_time} (${hoursRemaining.toFixed(1)} hours remaining)

## Current Tally (uregen)
- Yes: ${tally.yes} (${pct(tally.yes, totalVoted)}%)
- No: ${tally.no} (${pct(tally.no, totalVoted)}%)
- Abstain: ${tally.abstain} (${pct(tally.abstain, totalVoted)}%)
- No with Veto: ${tally.no_with_veto} (${pct(tally.no_with_veto, totalVoted)}%)

## Participation
- Total voted: ${totalVoted.toString()} uregen
- Bonded tokens: ${bondedTokens} uregen
- Current turnout: ${turnout.toFixed(1)}%
- Quorum required: 33.4%
- Quorum met: ${turnout >= 33.4 ? "YES" : "NO"}

${previousSnapshot ? `## Previous Snapshot\n${previousSnapshot}` : "## No previous snapshot available."}

Provide:
1. A 2-sentence summary of current state
2. Whether quorum is at risk
3. Projected outcome (PASS/FAIL/UNCERTAIN) with confidence (0.0-1.0)
4. Alert level (NORMAL/HIGH/CRITICAL) based on quorum risk, close vote, or time pressure
5. Key changes since last snapshot (if available)

Format as a concise markdown report.`;
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Untrusted data from proposal.content.title and other fields are concatenated into the prompt here, which is vulnerable to prompt injection. See the comment on line 43 for more details and remediation steps.

Comment on lines +146 to +174
const prompt = `Generate a post-vote analysis report for this finalized Regen Network governance proposal.

## Proposal
- ID: #${proposal.id}
- Title: ${proposal.content.title}
- Type: ${proposal.content["@type"]}
- Outcome: ${outcome}

## Description
${proposal.content.description.slice(0, 2000)}

## Final Tally (uregen)
- Yes: ${tally.yes} (${pct(tally.yes, totalVoted)}%)
- No: ${tally.no} (${pct(tally.no, totalVoted)}%)
- Abstain: ${tally.abstain} (${pct(tally.abstain, totalVoted)}%)
- No with Veto: ${tally.no_with_veto} (${pct(tally.no_with_veto, totalVoted)}%)
- Total turnout: ${turnout.toFixed(1)}%

## Vote Count
- Total distinct voters: ${votes.length}

Provide:
1. Executive summary (2-3 sentences)
2. Analysis of the result — was it decisive or contested?
3. Turnout analysis — high/low compared to typical Regen proposals
4. Implications — what does this mean for the network going forward?
5. Notable patterns (e.g., high veto %, low turnout, etc.)

Format as a structured markdown report with ## headings.`;
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Untrusted data from proposal.content.description is concatenated into the prompt here, which is vulnerable to prompt injection. See the comment on line 43 for more details and remediation steps.

Comment on lines +188 to +230
function buildAnalysisPrompt(
proposal: Proposal,
tally: TallyResult,
bondedTokens: string
): string {
const totalVoted =
BigInt(tally.yes) +
BigInt(tally.no) +
BigInt(tally.abstain) +
BigInt(tally.no_with_veto);

return `Analyze this Regen Network governance proposal in detail.

## Proposal Data
- ID: #${proposal.id}
- Title: ${proposal.content.title}
- Type: ${proposal.content["@type"]}
- Status: ${proposal.status}
- Submitted: ${proposal.submit_time}
- Voting Start: ${proposal.voting_start_time}
- Voting End: ${proposal.voting_end_time}
- Deposit: ${proposal.total_deposit.map((d) => `${d.amount} ${d.denom}`).join(", ")}

## Description
${proposal.content.description.slice(0, 3000)}

## Current Tally (uregen)
- Yes: ${tally.yes} (${pct(tally.yes, totalVoted)}%)
- No: ${tally.no} (${pct(tally.no, totalVoted)}%)
- Abstain: ${tally.abstain} (${pct(tally.abstain, totalVoted)}%)
- No with Veto: ${tally.no_with_veto} (${pct(tally.no_with_veto, totalVoted)}%)
- Bonded tokens: ${bondedTokens}

Provide a comprehensive analysis with:
1. **TL;DR** — One-sentence summary
2. **Category** — parameter_change | software_upgrade | community_pool_spend | credit_class | currency_allowlist | text_signaling | other
3. **Impact Assessment** (Technical / Economic / Governance — rate each Low/Medium/High with explanation)
4. **Risk Factors** — What could go wrong?
5. **Historical Context** — How does this compare to previous Regen proposals?
6. **Stakeholder Impacts** — Who benefits, who bears risk?

Format as a structured markdown document.`;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Untrusted data from proposal.content.description is concatenated into the prompt here, which is vulnerable to prompt injection. See the comment on line 43 for more details and remediation steps.

POLL_INTERVAL_SECONDS=300

# Optional: Claude model to use (default: claude-sonnet-4-5-20250929)
ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The default model name claude-sonnet-4-5-20250929 appears to be invalid. The versioning and future date suggest it might be a placeholder or a typo. To prevent runtime errors for users setting up the agent, it would be better to use a known valid model. For example, the latest Sonnet model is claude-3.5-sonnet-20240620.

ANTHROPIC_MODEL=claude-3.5-sonnet-20240620

Comment on lines +49 to +63
async getVotingProposals(): Promise<Proposal[]> {
return this.listProposals("2"); // PROPOSAL_STATUS_VOTING_PERIOD
}

async getPassedProposals(): Promise<Proposal[]> {
return this.listProposals("3"); // PROPOSAL_STATUS_PASSED
}

async getRejectedProposals(): Promise<Proposal[]> {
return this.listProposals("4"); // PROPOSAL_STATUS_REJECTED
}

async getDepositProposals(): Promise<Proposal[]> {
return this.listProposals("1"); // PROPOSAL_STATUS_DEPOSIT_PERIOD
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The methods for fetching proposals by status (e.g., getVotingProposals) use magic strings like "2", "3", etc. This reduces code readability and maintainability, as the meaning of these numbers isn't immediately clear and they could change in future API versions.

It's recommended to define these statuses as a constant enum-like object and reference them by name.

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.

2 participants