Skip to content

feat(assistant): tool-calling LLM assistant replacing NLP intent parser - #337

Open
sammajayi wants to merge 3 commits into
Neurowealth:mainfrom
sammajayi:feat/llm-tool-calling-assistant-318
Open

feat(assistant): tool-calling LLM assistant replacing NLP intent parser#337
sammajayi wants to merge 3 commits into
Neurowealth:mainfrom
sammajayi:feat/llm-tool-calling-assistant-318

Conversation

@sammajayi

Copy link
Copy Markdown
Contributor

Summary

Replaces the rule-based NLP parser (src/nlp/parser.ts) with a tool-calling LLM assistant as the recognition layer for open-ended requests, while preserving the platform's core integrity property: no money moves and no decision is attributed to the agent unless it went through the same verified, audited, idempotent paths every other feature uses. The model is a free-form planner; the codebase is the only executor.

  • Tool registry (src/agent/tools/): typed, Zod-schema'd wrappers over existing service-layer functions only — read tools (portfolio_value, positions, transactions, protocol_rates, goal_status, followed_strategy) and action tools (deposit, withdraw, rebalance, create_recurring_deposit, create_alert_rule, adjust_strategy, follow_strategy, unfollow_strategy). A structural test (tests/unit/agent/tools/structural.test.ts) asserts no tool imports stellar/contract or stellar/wallet directly, and every non-read-only tool requires confirmation.
  • Planner (src/agent/assistant/planner.ts): Anthropic tool-calling; every proposed call is re-validated against the tool's own Zod schema before anything runs — an out-of-schema or hallucinated tool name is rejected as a grounded error, never executed.
  • Confirmation gate (src/agent/assistant/confirmations.ts): every action tool is dry-run and parked, and only executes on an explicit affirmative reply to the exact confirmed proposal, with a callId-based idempotency guard against a duplicated confirmation. This is the load-bearing safety property and is covered by its own test suite.
  • Sub-account delegation: each tool declares a SubAccountPermission; checkSubAccountPermission() was extracted from src/middleware/subAccount.ts so the assistant orchestrator can enforce the identical permission rule outside of Express middleware (including for dry-run previews).
  • Conversation memory (Redis-backed, TTL'd) and per-user/global token budgets, degrading to a graceful fallback reply — never a dead bot — when the model is unavailable or the budget is exhausted. Prometheus metrics for token spend, tool latency, and fallback rate.
  • Audit: every executed tool call writes an AgentLog row.
  • Channels: wired into src/whatsapp/handler.ts and src/telegram/handler.ts as an additive fallback — gated behind ASSISTANT_ENABLED (default off) and only reached for the unknown intent bucket, so every command the existing parser already recognizes is completely unaffected. New POST /api/v1/assistant/chat REST endpoint for the web app, documented in docs/openapi.yaml.
  • Small supporting refactor: extracted executeWithdraw from processOnChainTransaction in src/controllers/transaction-controller.ts (mirroring the existing executeDeposit) so the withdraw tool has a proper service-layer entry point instead of a bespoke path.

Test plan

  • tests/unit/agent/tools/structural.test.ts — no tool imports raw stellar primitives; every tool declares a sub-account permission; every non-read-only tool requires confirmation
  • tests/unit/agent/assistant/confirmations.test.ts — confirmation scoping, TTL expiry, ambiguous-reply handling, executed-call idempotency
  • tests/unit/agent/assistant/planner.test.ts — schema validation, hallucinated-tool rejection, strict-schema extra-field rejection
  • tests/integration/agent/assistant.integration.test.ts — grounded read-tool answers, dry-run-then-confirm execution flow (through the real service path), cancellation, hallucination rejection, model-failure fallback
  • Full existing suite green: 86 suites / 1257 tests passed
  • npm run build / npm run lint clean

Closes #318

Adds a tool-calling conversational assistant (src/agent/assistant/,
src/agent/tools/) that replaces src/nlp/parser.ts as the recognition
layer for open-ended requests on WhatsApp, Telegram, and a new
POST /api/v1/assistant/chat endpoint, while keeping the platform's
core integrity property: no money moves and no decision is attributed
to the agent unless it went through the same verified, audited,
idempotent paths every other feature uses.

- Tool registry (src/agent/tools/): typed, Zod-schema'd wrappers over
  existing service-layer functions only (deposit/withdraw via
  executeDeposit/executeWithdraw, rebalance via executeRebalanceIfNeeded,
  strategy follow/unfollow, alerts, recurring deposits, and read tools
  for portfolio/positions/transactions/protocol rates/goals). A
  structural test asserts no tool imports stellar/contract or
  stellar/wallet directly.
- Planner (src/agent/assistant/planner.ts): Anthropic tool-calling,
  schema-validates every proposed call before anything runs; an
  out-of-schema or hallucinated tool is rejected, never executed.
- Confirmation gate (src/agent/assistant/confirmations.ts): every
  action tool is dry-run and parked, and only executes on an explicit
  affirmative reply to the exact confirmed proposal, with a
  callId-based idempotency guard against a duplicated confirmation.
- Sub-account delegation: each tool declares a SubAccountPermission,
  enforced via a new checkSubAccountPermission() extracted from
  src/middleware/subAccount.ts so the assistant orchestrator can gate
  delegated actions outside of Express middleware.
- Conversation memory (Redis-backed, TTL'd) and per-user/global token
  budgets, degrading to a graceful fallback reply (never a dead bot)
  when the model is unavailable or the budget is exhausted.
- Every executed tool call writes an AgentLog row.
- WhatsApp/Telegram wiring is additive and gated behind
  ASSISTANT_ENABLED (default off): the existing rule-based parser
  still handles every intent it already recognizes unconditionally;
  only the 'unknown' bucket is routed to the assistant.
- docs/openapi.yaml documents POST /api/v1/assistant/chat.

Closes Neurowealth#318
@Abidoyesimze

Copy link
Copy Markdown
Contributor

Pls fix conflict

@sammajayi

Copy link
Copy Markdown
Contributor Author

@Abidoyesimze done

The merge of main into this branch left env.ts, transaction-controller.ts,
and actionTools.ts with concatenated/duplicated conflict content instead of
properly resolved code: env.ts had a truncated assistant config block,
transaction-controller.ts had duplicate ExecuteWithdrawParams/Result
interfaces and a duplicate `transaction` declaration, and actionTools.ts
needed null checks now that ExecuteDepositResult/ExecuteWithdrawResult
carry a nullable transaction (PENDING_APPROVAL path from the approvals
feature merged in from main).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmbhG5UojzVeXWGLta8Evn
@sammajayi
sammajayi force-pushed the feat/llm-tool-calling-assistant-318 branch from f0c8727 to 55fa132 Compare August 29, 2026 20:22
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.

Conversational AI Portfolio Manager with Verified Tool-Calling

2 participants