mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Financial

Local-First Finance Agent Workflows: Execution Boundaries and State Persistence

How Ekselio-style builders keep financial data on-device while orchestrating agent tool calls, managing secrets, and maintaining audit trails.

Source: gptbeyond.com
Local-First Finance Agent Workflows: Execution Boundaries and State Persistence

Local-first agent workflow builders for finance promise to keep sensitive transaction data on your device while still enabling LLM-powered automation. The pitch is appealing: build Zapier-style workflows that reconcile expenses, categorize transactions, or generate tax reports without sending account balances to a cloud service. But the execution model gets complicated fast when you need to call external APIs, manage secrets, persist state across crashes, and maintain audit trails.

Ekselio positions itself as “Loveable for finance workflows,” suggesting a visual builder that generates agent code while respecting data residency constraints. The local-first architecture raises immediate plumbing questions: how do you orchestrate tool calls when financial APIs require server-side OAuth, where do you store workflow state without cloud sync, and what prevents an agent from leaking PII when it calls a third-party enrichment service?

Execution Topology

A local-first finance agent runs in one of three places:

  • Browser runtime: JavaScript execution in the client, limited to CORS-friendly APIs and browser storage
  • Desktop process: Electron or Tauri shell with filesystem access, native crypto libraries, and unrestricted network calls
  • Edge worker: Cloudflare Worker or Deno Deploy instance that runs close to the user but still requires network round-trips

The choice determines your tool execution boundary. Browser runtimes cannot safely store API keys or OAuth refresh tokens. Desktop processes can encrypt secrets with OS keychains but complicate distribution. Edge workers solve the secret storage problem but reintroduce cloud dependencies.

Most local-first builders choose the desktop process model. You ship a binary that embeds a workflow engine, a local SQLite database for state, and a secret vault backed by the OS credential manager. The agent runs entirely on the user’s machine. External API calls happen directly from the desktop process, not through a proxy server.

Tool Call Orchestration

Financial workflows typically need tools that fall into three categories:

Tool TypeExampleExecution Constraint
Read-only data fetchPlaid account balanceRequires OAuth token, safe to cache
Mutation with side effectsStripe payment creationRequires idempotency key, must log to audit trail
Third-party enrichmentMerchant categorization APIMay leak transaction details, needs PII filtering

The orchestration layer must handle each differently. Read-only tools can cache responses in local SQLite to avoid redundant API calls. Mutation tools need idempotency tracking so a workflow retry doesn’t double-charge a customer. Enrichment tools require a data sanitization step before the external call.

Here’s a simplified tool execution wrapper that enforces these constraints:

interface ToolCall {
  name: string;
  params: Record<string, unknown>;
  category: 'read' | 'mutate' | 'enrich';
}

async function executeTool(call: ToolCall, context: WorkflowContext): Promise<unknown> {
  // Check cache for read-only tools
  if (call.category === 'read') {
    const cached = await context.db.get('tool_cache', call.name, call.params);
    if (cached && !cached.expired) return cached.result;
  }

  // Generate idempotency key for mutations
  const idempotencyKey = call.category === 'mutate' 
    ? await context.db.getOrCreateIdempotencyKey(call.name, call.params)
    : null;

  // Sanitize params for enrichment calls
  const sanitizedParams = call.category === 'enrich'
    ? await sanitizePII(call.params, context.piiRules)
    : call.params;

  // Execute with secret injection
  const result = await context.toolRegistry.call(call.name, {
    ...sanitizedParams,
    _idempotencyKey: idempotencyKey,
    _secrets: await context.vault.getSecretsFor(call.name)
  });

  // Log to audit trail for mutations
  if (call.category === 'mutate') {
    await context.audit.log({
      tool: call.name,
      params: call.params,
      result: result,
      timestamp: Date.now()
    });
  }

  return result;
}

The key insight: tool execution is not a simple function call. You need a context object that carries the local database handle, the secret vault, the audit logger, and the PII sanitization rules. Each tool category gets a different execution path.

State Persistence Without Cloud Sync

Finance workflows often run for hours or days. A tax report generator might fetch transactions from multiple accounts, apply categorization rules, calculate deductions, and produce a PDF. If the workflow crashes halfway through, you need to resume without re-fetching all the data or re-running expensive LLM calls.

Local-first builders typically use a checkpoint-based state machine:

  1. Workflow definition: A DAG of steps with input/output schemas
  2. Execution state: A SQLite table tracking which steps completed and their outputs
  3. Checkpoint triggers: After each step completes, write its output to the state table
  4. Resume logic: On restart, skip completed steps and resume from the last checkpoint

The state table schema looks like this:

CREATE TABLE workflow_executions (
  id TEXT PRIMARY KEY,
  workflow_name TEXT NOT NULL,
  started_at INTEGER NOT NULL,
  completed_at INTEGER,
  status TEXT CHECK(status IN ('running', 'completed', 'failed'))
);

CREATE TABLE step_executions (
  execution_id TEXT NOT NULL,
  step_name TEXT NOT NULL,
  started_at INTEGER NOT NULL,
  completed_at INTEGER,
  input_hash TEXT NOT NULL,
  output BLOB,
  error TEXT,
  PRIMARY KEY (execution_id, step_name),
  FOREIGN KEY (execution_id) REFERENCES workflow_executions(id)
);

The input_hash column is critical. It lets you detect when a step’s inputs changed, invalidating the cached output. If a user edits the workflow definition or updates a parameter, the hash changes and the step re-runs.

Secret Management and OAuth Flows

Financial APIs require OAuth tokens that expire and refresh. A local-first agent cannot store these in plaintext SQLite. The standard solution is OS-level credential storage:

  • macOS: Keychain Services API
  • Windows: Credential Manager (CredWrite/CredRead)
  • Linux: libsecret with GNOME Keyring or KWallet backend

You store the OAuth refresh token in the OS vault and the access token in memory. When the access token expires, the agent uses the refresh token to get a new one without user interaction.

The tricky part is the initial OAuth flow. Most financial APIs use the authorization code grant, which requires a redirect URI. A desktop app cannot host a public HTTPS endpoint. The workaround is a local loopback server:

  1. Agent starts a temporary HTTP server on http://localhost:random-port
  2. Opens the OAuth provider’s authorization URL in the default browser
  3. User approves the request
  4. Provider redirects to http://localhost:random-port?code=...
  5. Agent exchanges the code for tokens and shuts down the server

This works but creates a poor user experience. Some builders use a cloud-hosted OAuth proxy that stores only the refresh token and returns it to the desktop app via a one-time code. This reintroduces a cloud dependency but avoids storing long-lived tokens on a server.

Version Control for Workflow Definitions

Finance workflows often embed proprietary logic: custom categorization rules, tax calculation formulas, or fraud detection heuristics. You cannot commit these to a public Git repo. But you still need version control for collaboration and rollback.

Local-first builders solve this with encrypted workflow bundles:

  • Workflow definition is a JSON or YAML file
  • Sensitive fields (API keys, calculation formulas) are encrypted with a team key
  • The bundle is committed to Git with encrypted blobs
  • Developers decrypt locally using a key stored in their OS vault

The encryption happens at the field level, not the file level. This lets you diff workflow changes in Git without exposing secrets. A typical encrypted field looks like:

steps:
  - name: calculate_tax
    tool: custom_formula
    params:
      formula: "ENC[AES256:base64-ciphertext]"
      rate: 0.21  # plaintext, not sensitive

The builder UI decrypts the formula when rendering the workflow editor and re-encrypts on save.

Data Sanitization for Third-Party Tools

Merchant categorization APIs are useful but risky. You send a transaction description like “WHOLE FOODS MARKET #12345” and get back a category like “Groceries.” The API provider now knows where you shop.

A local-first agent should sanitize transaction details before sending them to third-party enrichment services:

  • Strip merchant IDs and location codes
  • Normalize amounts to ranges (e.g., “$47.23” becomes “$40-50”)
  • Hash account numbers and replace with stable pseudonyms
  • Remove timestamps or round to the nearest day

The sanitization rules are part of the workflow definition. Each third-party tool declares what fields it needs, and the agent applies the minimum necessary transformation.

Audit Trail Requirements

Financial workflows must log every action for compliance and debugging. The audit trail needs:

  • Immutability: Append-only log that cannot be edited after writing
  • Completeness: Every tool call, every decision, every error
  • Queryability: Fast lookups by date range, workflow ID, or tool name

SQLite with a write-ahead log (WAL) provides immutability and queryability. You create an append-only table with a trigger that prevents updates and deletes:

CREATE TABLE audit_log (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  timestamp INTEGER NOT NULL,
  execution_id TEXT NOT NULL,
  step_name TEXT NOT NULL,
  tool_name TEXT,
  action TEXT NOT NULL,
  details TEXT,
  CHECK (id > 0)  -- Prevents manual ID assignment
);

CREATE TRIGGER prevent_audit_modifications
BEFORE UPDATE ON audit_log
BEGIN
  SELECT RAISE(ABORT, 'Audit log is immutable');
END;

CREATE TRIGGER prevent_audit_deletions
BEFORE DELETE ON audit_log
BEGIN
  SELECT RAISE(ABORT, 'Audit log is immutable');
END;

The details column stores a JSON blob with tool inputs, outputs, and errors. You can query it with SQLite’s JSON functions for debugging.

Failure Modes

Local-first finance agents fail in predictable ways:

FailureSymptomMitigation
OAuth token expires during long workflowAPI calls fail mid-executionStore refresh token, retry with fresh access token
User closes app during mutation stepPartial state, duplicate chargesUse idempotency keys, checkpoint before mutations
Third-party API changes response schemaTool execution throws parse errorVersion tool schemas, validate responses before caching
SQLite database corruptsWorkflow state lostEnable WAL mode, periodic backups to user-controlled storage

The most insidious failure is silent data leakage. If a workflow calls an enrichment API without sanitizing inputs, you’ve violated data residency guarantees. The mitigation is mandatory sanitization hooks in the tool execution layer.

Technical Verdict

Use local-first finance agent builders when:

  • You need to process sensitive financial data without cloud storage
  • Workflows are user-specific and do not require team collaboration
  • You can distribute a desktop app and manage OS-specific secret storage
  • Audit requirements allow local-only logs with user-controlled backups

Avoid when:

  • Workflows need real-time collaboration or centralized policy enforcement
  • You rely on cloud-only APIs that block requests from residential IPs
  • Users expect mobile access or cross-device sync
  • Compliance requires server-side audit logs with tamper-proof timestamps

The local-first model works well for personal finance automation and small business bookkeeping. It breaks down when you need centralized control, real-time collaboration, or integration with cloud-native financial platforms that expect server-to-server authentication.