This article continues Building a Dedicated Workspace for Hermes to Work Reliably. The previous article explained why I separated Hermes from a deterministic execution plane. Here I apply the same pattern to a concrete workflow: recording transactions in a Financial Planner spreadsheet.
The problem is not only writing one row
Recording a transaction looks simple. I could open the spreadsheet, find an empty row, and enter an amount and description.
The problems appear when the same action is repeated:
- category names need to stay consistent;
- source and destination accounts must not be reversed;
- a transfer must not become income or an expense;
- a new receivable and its repayment need a clear relationship;
- amounts and dates need one consistent format;
- changes to an external spreadsheet need an audit path;
- an agent must not treat an ambiguous message as permission to write.
I therefore do not make Hermes a free-form spreadsheet editor. Hermes understands the message and helps prepare the transaction. The workspace and CLI enforce the rules, request approval, perform the write, and read the result back.
How this relates to the Hermes workspace article
The architecture follows the same boundary described in the previous article:
Hermes handles conversation and routing. The CLI is a contract that can be called manually or by a scheduler. The spreadsheet remains the record, but it does not receive a direct prompt-driven write outside the lifecycle.
The input format I use
I do not need a long command format for every transaction. A natural-language message is usually enough when the required fields are clear.
For example:
Paid 35,000 for lunch from the main account
Or:
Received a freelance payment of 2,500,000 into the main account
The message does not immediately become a spreadsheet row. The agent first normalizes the intent and checks that required information is present.
When a transaction is ambiguous, I prefer a question over a guess. For example, if two accounts have similar names or a category cannot be mapped confidently, the workflow stops at the draft stage.
Metadata is read before every transaction
Account and category values are not a list to guess from conversational memory. Before creating a plan, the workflow reads the active Financial Planner metadata.
Conceptually, that metadata contains:
- valid accounts;
- valid categories;
- cashflow rules;
- other field choices required by the spreadsheet;
- the active setup configuration.
The command is:
.venv/bin/kw finance metadata --json
The JSON output becomes the validation source. If the spreadsheet uses a different category name from the everyday phrase, the adapter maps it according to metadata instead of silently creating a new category.
This also protects the workflow from common personal-spreadsheet problems: one category appearing with several spellings, a destination account being written to the wrong field, or an old transaction using a value that is no longer valid.
Mapping intent to a cashflow rule
One transaction message should match one cashflow rule. I do not let one message become several cashflow types without an explicit decision.
Conceptually, a rule may represent types such as:
| Intent | Example | Required clarity |
|---|---|---|
| Expense | Paying for lunch | amount, category, source account |
| Income | Receiving a payment | amount, category, destination account |
| Transfer | Moving money between accounts | source and destination accounts |
| New receivable | Lending money | amount, category, source account, person |
| Receivable repayment | Someone pays a debt | original receivable and destination account |
The actual rule names and available fields still come from live metadata. This table describes the reasoning, not a replacement for the spreadsheet configuration.
The difference between source and destination accounts matters. For an expense, money leaves the From Account. For income, money enters the To Account. For a transfer, both sides are required and the transaction must not be counted as new income.
From a message to a frozen payload
After metadata and the rule match, the workflow creates a plan. The plan stores the payload that will be used if I approve the transaction.
A conceptual payload looks like this:
{
"cashflow_type": "expense",
"date": "2026-08-04",
"description": "Lunch",
"amount": 35000,
"category": "<category-from-live-metadata>",
"from_account": "<account-from-live-metadata>",
"to_account": null
}
The account and category values are intentionally not real data in this example. In an actual execution, those values must come from the active setup metadata.
The payload is then frozen with an action ID and hash. After approval, the workflow does not rebuild the transaction from the conversation context. It executes the same payload.
The plan, approve, execute, verify lifecycle
This is the part of the workspace design I preserve:
metadata
→ normalize intent
→ match one cashflow rule
→ plan frozen payload
→ show fields for review
→ explicit approval
→ execute spreadsheet write
→ read-back verification
1. Metadata
The workflow retrieves current account, category, and rule choices.
2. Normalize intent
The natural-language message becomes structured fields: cashflow type, date, description, amount, and the relevant account fields.
3. Plan
The CLI creates a durable action. The payload, hash, and action ID are stored before the external write.
4. Review
I see a summary of the transaction that will be recorded. I use bullets so it remains easy to review from Telegram or a mobile device:
Type: Expense
Date: 2026-08-04
Description: Lunch
Amount: Rp35,000
Category: <from metadata>
From Account: <from metadata>
5. Approval
Words such as ok or approve count as approval only when the context is unambiguous. If there are multiple drafts or an uncertain field, the workflow does not guess which transaction was intended.
6. Execute
The approved action executes with the frozen payload. No field is silently changed between approval and execution.
7. Verify
The adapter reads the result back or checks the evidence returned by the spreadsheet. Completion is reported only when both the action and verification succeed.
Why not call the Google Sheets API directly?
Technically, an agent could call the Google Sheets API directly. That approach mixes several responsibilities:
- the prompt starts defining spreadsheet structure;
- category validation becomes inconsistent;
- approval is reduced to a sentence in the conversation;
- retries and verification are easy to skip;
- changes are difficult to reproduce from logs.
With a CLI, Hermes only needs a small contract. The skill routes the conversation to the finance command, while the workspace implementation handles metadata, payloads, the spreadsheet adapter, and verification.
Hermes skill
→ kw finance metadata
→ kw finance plan
→ explicit approval
→ kw action execute
→ verified transaction result
The skill stays thin. It explains when to use the workflow and which rules matter, but it does not store transaction business logic inside a prompt.
A special case: receivable repayment
I do not record a receivable repayment as ordinary income. The workflow first needs to find the matching original receivable.
For example:
Andi paid back 500,000 into the main account
If exactly one matching new-receivable transaction exists, the workflow can map it as follows:
- use the receivable repayment type;
- inherit the category from the original receivable;
- use the receiving account as the destination account;
- preserve the relationship with the original transaction.
If there is no single match, the workflow asks for clarification. Recording it as general income just because money arrived would damage the history and cashflow analysis.
What this design gives me
The spreadsheet stays simple for a human, while the surrounding process has clear boundaries:
- Hermes understands the message;
- metadata keeps choices valid;
- rules distinguish income, expense, transfers, and receivables;
- a plan makes the change reviewable;
- approval prevents an unauthorized write;
- the adapter handles spreadsheet details;
- verification confirms that the record was actually saved.
This is not an attempt to make finance tracking more complicated. The goal is to move complexity from an inconsistent conversation into a workflow that can be tested.
Boundaries and security
A finance workflow should not treat a spreadsheet as a safe place for everything. I keep several boundaries:
- spreadsheet IDs and credentials never enter the repository;
- OAuth tokens stay in local secret storage;
- transaction data is not sent to public channels;
- confirmations do not show combined balances unless requested;
- read-only commands are separated from external writes;
- approval cannot be skipped because a scheduler or agent thinks the transaction looks obvious.
For operations originating from email, an import job creates only a draft or pending_review state. Email detection does not automatically become a recorded transaction.
Closing thoughts
Financial recording is a small example of the principle from the Hermes workspace article: an agent can be flexible when understanding a goal, but external execution needs a contract, state, approval, and evidence.
With this pattern, I can write a transaction message naturally without turning the spreadsheet into an experiment area. Hermes helps me speak to the system. The CLI and adapter ensure that the Financial Planner rules are followed.