Source code: Tabforge AI
Most AI integrations in business apps feel the same:
You send a prompt → the model returns an answer → you try to glue it into your app.
It works, but it always feels slightly disconnected from what’s actually happening in the UI.
The AI doesn’t really know what the user is doing.
And the app doesn’t really know what the AI just did.
We ran into that problem while building EasyAI / TabForge, and ended up with something we didn’t originally set out to build:
a bidirectional context loop between the application and the AI runtime.
The problem: AI in apps is stateless
Even when you pass “context”, it’s usually:
- a string
- a JSON blob
- or a manually assembled prompt
But the real state lives elsewhere:
- which record is open
- what tab the user is on
- what action just happened
- what step in a workflow the user is in
So you end up doing things like:
- passing IDs around
- re-sending state on every request
- rebuilding context for every AI call
It works, but it’s fragile.
**
Ambient Activity Memory (App → AI)**
The first thing we added was a way for the application to continuously describe what is happening inside it.
Not as logs.Not as analytics.
But as structured semantic events tied to actual UI actions.
So instead of:
“here is an order id”
the system already knows:
- user is currently viewing Order #248
- user just switched from payment tab to details tab
- user just triggered refund flow
Now when the user says:
“cancel this order”
there is no ambiguity about what “this” refers to. The AI doesn’t guess context.It already has it.
EasyAIEvent (AI → App)
Once the AI started understanding the app state, the next obvious question was:
what does the AI give back to the application?
Not just a final answer, but the execution itself.
So every agent run can optionally emit a structured event stream:
- started
- planning
- tool calls
- progress updates
- completion This is exposed via a simple hook:
.withEventListener(event -> {
log.info("[{}] {} — {}", event.source(), event.phase(), event.title());
})
No framework coupling.No HTTP assumptions.No UI dependencies.
Just a pure event stream that your app can consume however it wants.
What this enables (more interesting part)
Once you have both directions:
App → AI
The system knows what the user is doing.
AI → App
The system exposes what the agent is doing. You end up with something simple but powerful:
a closed loop between UI state and AI execution state
**
Why this matters in practice**
This removes a bunch of glue code that usually creeps into AI integrations:
- passing IDs back and forth
- manually constructing prompts
- debugging “what did the agent actually do?”
- rebuilding context on every request
Instead:
- the app continuously emits context
- the AI continuously emits execution state
- both stay decoupled, but synchronized
Important design choice
The event model is intentionally framework-agnostic:
(source, phase, status, title, detail, toolName, sequence, timestamp)
It does not know anything about:
HTTP, WebSockets, SSE, UI frameworks
That part is left to the application.
We ship a minimal example that maps the event stream to a real-time UI panel using SSE, but it stays outside the core library.
Where this is going
The interesting part is not the event system itself. It’s what becomes possible when:
- the app knows what the user is doing
- the AI knows what it is doing
- and both sides share the same runtime context
You start to move from “AI calls inside an app” toward something closer to:
AI as a participant in application execution, not just a function you call
If you strip everything away
At its core, this is all we tried to solve:
How do we make AI systems aware of application state without coupling them to the UI?
And the answer turned out to be:
Don’t pass state. Stream it in both directions.
If you want to explore it:
GitHub: https://github.com/tabforgeai/tabforge-ai
Full example: https://github.com/tabforgeai/tabforge-ai-demo























