BlogAnton Ignashev

How I Built an AI Accounting Agent — the Architecture From Telegram to the ERP

How I Built an AI Accounting Agent — the Architecture From Telegram to the ERP

What We're Taking Apart

In the LUBART Keg Market case study I described what the accounting agent does: the accountant, the director and the owner ask questions on Telegram, the agent answers in seconds from live enova365 data, and every morning at 8:00 a digest turns up without anyone asking for it.

This article is the other half. How it's built — component by component, the decisions behind each one, and the two or three things that will bite you if you go and build your own.

The architecture, short version:

  • an agent runtime in a single Kubernetes pod,
  • tools implemented as Python skills the LLM can call,
  • a read-only REST connector into enova365 with a two-token auth flow,
  • CronJobs that turn a conversational agent into a proactive reporting system,
  • a Telegram delivery layer restricted to an allowlist of chats.

No vector database. No fine-tuning. No separate BI stack. Boring pieces, chosen on purpose.


The Runtime: One Pod, One Config, Skills on a Volume

The agent runs as a single container in Kubernetes. Its behaviour lives in one YAML config — system prompt, workflows, enabled skills — and the skills themselves are Python modules sitting on a persistent volume.

A skill is a plain module that exports a tool schema (name, description, parameters) and an async execute(args) function. That's the entire plugin contract. The LLM sees the schema, decides when to call the tool, the runtime runs the Python. Adding a capability means dropping one file onto the volume. No rebuild, no redeploy.

Sounds like a minor convenience. It isn't. The agent went from "answers questions" to "sends charts and spreadsheets" without anyone touching the core — new skill file, one line in the config, done.


The ERP Connector: Read-Only by Construction

The agent's only route into enova365 is a REST layer, and its token carries read rights and nothing else. This isn't a policy written into a prompt — prompts can be argued with. It's the shape of the credential itself.

Two details of the auth flow deserve your attention, because both of them bite.

The two-token dance. A long-lived application JWT is used for exactly one thing: logging in. The login returns a session token that lives for minutes. Every successful response then rolls that session token — the fresh one arrives in a response header, and your client has to capture it and reuse it. Miss that, and your integration works for precisely one call at a time, relogging on every request and looking like a brute-force attempt in the logs.

Rights are per controller, not per token. The application token carries a list of service names it's allowed to touch. If a controller isn't on that list, you don't get a login failure. Login succeeds. Then that one controller returns 401 while everything else keeps working. The first time it happens it looks exactly like a bug in your code. It isn't. Go and check the token's service list before you debug anything else.

The connector keeps its own whitelist of endpoints too, so the agent can call only the queries it genuinely needs: contractors, sales and purchase invoices with settlement state, payments, stock. As far as the LLM is concerned, nothing else exists.


Proactivity: CronJobs Talking to the Agent

A chat agent answers when someone asks. But the information worth the most is the kind nobody thought to ask for — yesterday's sales, the invoices that went overdue overnight, the payments landing this week.

The mechanism is almost embarrassingly simple. Kubernetes CronJobs (workdays at 8:00, Mondays at 8:30 for the weekly review) POST a digest prompt to the agent's own chat-completions endpoint, with dedicated session IDs so scheduled runs never contaminate real conversations. The agent handles that prompt exactly as if a user had typed it — same ERP tools, same formatting — and ships the result through the Telegram skill.

One agent, one set of tools, two modes of operation. The alternative is a separate reporting pipeline duplicating every ERP query, which is how you end up maintaining two systems that quietly disagree with each other.


Charts and Files: the Last Mile Is a Skill Too

"Show me the sales trend" should never end with "log into the BI portal". So the agent generates matplotlib charts as PNG and openpyxl spreadsheets as XLSX inside its workspace, and a Telegram sender skill delivers them as photos and documents — same allowlist as messages, and it may only read files from the agent's own workspace directory.

That last constraint is easy to skip over. Don't. A file-sending tool with no path restriction is an exfiltration primitive waiting for someone to find it.


Security Posture, Summarized

  • Read-only ERP token. The agent cannot create, modify or post anything. Not "shouldn't" — cannot.
  • Telegram allowlist — only explicitly listed chats can talk to the agent or receive digests. DMs from strangers get dropped.
  • Endpoint whitelist in the connector, so the LLM can't improvise new queries.
  • Short-lived rolling session tokens — a leaked token ages out in minutes.
  • EU hosting, minimal scopes. RODO-compatible by construction, not by disclaimer.

None of this is exotic. And that's rather the point: it's the reason a company let an AI agent anywhere near its books in the first place. Trust is a property of the architecture long before it's a sales argument. I wrote more about that side in the deployment story.


What I'd Do Differently

Start with the digests, not the Q&A. People needed a few days to build the habit of asking anything at all. The morning digest delivered value on day one — and, as a bonus, taught them what sort of question the agent could handle.

Budget a day for the auth flow. The rolling-token behaviour is documented, but every client library out there assumes a token doesn't change on every request. Write the capture logic first, not after a morning of mysterious 401s.

Keep the tool surface small. The temptation is to expose every ERP endpoint "just in case". Resist it — every extra tool dilutes the LLM's tool-selection accuracy. The agent got more reliable when I removed the queries nobody used.

If you run enova365 and want the deeper ERP-side details, the developer's guide to the WebAPI covers licensing, controllers and failure modes. And if you'd rather have the outcome than the build: that's the service — a free 30-minute audit tells you which document flow pays back first.

Let’s talk about your project

Free 30-minute consultation. We’ll figure out if and how I can help.

Book a Free 30-Minute Call

Select a date

August 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Back to Blog

Related Posts

Comarch Optima API: a developer's guide to integrating Optima
Blog

Comarch Optima API: a developer's guide to integrating Optima

The question is usually whether Optima has an API. The answer is that it has five different things by that name, each with a different licence, a different owner, and a different phone number to call when it stops working.

Read more
Which AI agent to build first against enova365
Blog

Which AI agent to build first against enova365

The most valuable agent is almost always the wrong first build. Not because it cannot be built, but because its first honest output arrives in week ten, and nothing holds a room's attention that long. Four questions that screen the candidates, and one number that predicts whether the project lands.

Read more
The enova365 service account — what read-only actually means
Blog

The enova365 service account — what read-only actually means

The integration gets the Administrator account, because scoping the rights would take an afternoon and nobody has the afternoon. Then it turns out the strongest read-only boundary in enova365 is not the permission tree at all. It is the licence.

Read more