Actionary is designed agent-first. The substrate describes the business as data. The embedded agent reasons over that data through the same contract the UI renders on — one entity-configuration payload feeds the SPA, the admin REST API, and the MCP tool surface. Same schema. Same permissions. Same audit trail.

Adding a field to a Warranty Claim entity surfaces it in the sidebar, the list view, the modal, the search index, and the agent’s tool catalogue — automatically, because there is nothing to update.

The In-App Agent Client Speaks MCP and it’s the contract

The tool surface is the MCP tool surface, gated by OAuth + Dynamic Client Registration + PKCE on its own hostname. Any DCR-compliant MCP client registers once and calls the same catalogue the embedded agent sees. One OAuth flow. One set of tool schemas. One permission model.

Because the business is described as data, the MCP tools work on any entity — propose-entity-update and execute-entity-update operate on Deals and on a tenant-defined Warranty Claim uniformly. Registering a new entity through Schema Admin extends the agent’s reach the same instant it extends the UI’s. Every tool declares its role permissions in one central allowlist, and the same authorisation decorators that gate the browser gate the agent. If the caller cannot do it, the agent cannot do it. Every agent action runs under the caller’s own permissions — one code path, one authorisation check.

Reasoning proposes, the platform lands — durably

The agent proposes actions. The platform lands them exactly once. Firing the same tool call twice produces the same outcome as firing it once. The propose-entity-update tool stages a diff in Redis with a short TTL; a separate execute-entity-update call commits it. The LLM proposes. The operator or a downstream check confirms. The platform applies.

The destructive-confirm gate is a per-conversation, per-tool-use uniqueness constraint on a pending-confirmation table. Delete, archive, revoke, bulk — the gate matches on tool-name pattern, generic across entities. The crucial property: the pending tool-use block stays out of the assistant message until confirmation lands. The message protocol stays self-consistent across the pending → confirmed → tool-result transition, so a replay hours later still finds a well-formed conversation with no orphan calls.

Deeper mechanics — FOR UPDATE SKIP LOCKED step claims in the workflow engine, an idempotency-key header on public writes, HMAC-signed retriable webhooks with a dead-letter ledger, and deliberate do-nothing-on-conflict clauses across every live-row primitive — are the durable execution story.

The in-app assistant knows what you’re looking at

Open any record in Actionary — an account, a page, an invoice, a filtered list — and hit ⌘J. The assistant slides in without leaving the record. Ask “summarise this” and it summarises the record you’re on. Ask “how many of these are overdue?” on a filtered list and it counts the exact filtered subset. Words like this, here, the record resolve automatically because every turn carries the current URL context to the model: the entity, the record id with its human display name, and the full list-view state (filter chips, ownership scope, sort, search query). No copy-pasting the record id. No re-stating context every turn.

The view-context payload is validated against the tenant’s entity catalog before it’s sent — a URL under /settings or /agent leaks no bogus context. Different turns can reference different records; the per-turn placement (never in the cached system prompt) preserves the tenant’s prompt cache economics so the additional intelligence costs nothing structural.

The agent edits a passage, and a refusal hands back the truth

Changing one paragraph of a long document by rewriting the whole document is slow, and every rewrite is a chance to reword something nobody asked to change. So the agent names the passage to replace, and the platform substitutes it — the text moves between the tool surface and the API, never through the model.

That contract asks the model to quote existing text exactly, and it must match once. Two failure modes follow, and both are answered mechanically rather than with a warning in a prompt.

A refusal returns what the caller needed. The tool has already read the record in order to match, so on a miss it is holding the very text the model was missing. It returns it. The next attempt is composed against what is stored rather than against a memory of it. The alternative — answering “no match” and nothing else — leaves a model that cannot see the record reasoning about why, and reasoning without the document is invention: it reports a platform limitation and tells the person to do it by hand.

A compacted value is a fact about the field, never a piece of it. A long conversation outgrows the context window, so older tool results become a digest. A long value in that digest is replaced by its length — body_html=<6179 chars, not shown &mdash; re-fetch to quote> — never by its opening characters. A prefix reads as content, so a model quotes from it, completes it from what it saw rendered, and names text the record does not contain. Nothing in a prefix says it is a prefix. Short values stay whole and quotable, which is what a digest is for.

Both follow one rule the platform applies elsewhere: a short thing that looks complete is worse than an honest refusal. The record exports refuse rather than truncate for the same reason.

Continuous-learning memory the operator can teach, not just observe

Every multi-tool agent turn writes a per-tenant reflection row: a 1024-dimension embedding, ordered tool sequence, referenced entities, referenced fields, trust tier, outcome. The next turn retrieves top-K reflections by hybrid score (cosine, trust-tier, outcome, recency, capped hit-count) and injects them as a past-experience block. Schema-drift triggers invalidate reflections whose referenced field is deleted or renamed, so the memory tracks the schema turn by turn.

The correction loop closes. Every reflection carries an editable guidance field the tenant admin populates from the memory page — “For Zendesk activities always set billing_status to ‘rolled up billing’” — and retrieval renders that guidance verbatim in place of the auto-captured tool sequence on future matches. The admin literally tells the model what to do differently and the model reads it on the next turn. Turning a downvote into a positive teaching moment ships end-to-end: an Evaluation tab surfaces flagged responses with a Correct this button that opens the guidance editor scoped to the failing turn.

Feedback is per-message, not per-conversation. A thumbs-up on message N updates only that message’s reflection — no conversation-wide bleed. And every user gets a star icon next to thumbs to suggest for playbook: the row lands in an admin review queue at the top of the memory page for Accept (promote to playbook + clear queue in one step) or Dismiss. Classic human-in-the-loop training — users give signals, admins take actions.

Operators see and govern every reflection at /settings/agent/memory — the card layout shows the user’s question, the agent’s actual answer (extracted from the linked message), the guidance, entities, tools, trust chip, hit count, and a View conversation deep-link. Edit, re-embed, promote to Curated or Playbook, invalidate, restore. Per-tenant embedding provider — OpenAI (enterprise default, covered by the OpenAI DPA), Voyage, or a self-hosted sidecar — all emit vectors at the same fixed dimension, so the column type is provider-agnostic.

RAG on the substrate — same gates, same audit log

The agent reads what the tenant uploads: PDFs, Word documents, PowerPoint decks, CSVs, JSON, plain text and Markdown, and every audio and video container the meeting recorder handles. A complete Retrieval-Augmented Generation stack built on the primitives that serve the rest of the platform.

Retrieval is HNSW cosine search over an upload-chunk store on the standard Postgres vector extension. Every query is filtered by the unified per-row visibility predicate — the same rule the SPA list endpoint applies — so an asset a caller cannot see cannot surface. Hits reach the agent through the search-upload-contents MCP tool with real page numbers (PDF, PowerPoint) or time offsets (audio, video), bounded per retrieval block. A citation reads “page 14 of the Q4 board pack” or “at 08:42 in the Acme discovery call” — the exact human-readable pointer the operator can open.

The RAG stack runs on the same Postgres, the same audit log, and the same visibility gates as every other read path in the product.

The layered safety perimeter

Defence in depth applied to every agent turn:

  • Typed tool-result framing — untrusted tool output is structurally distinguished from operator intent at the message-protocol level.
  • System-prompt directive — names the common injection patterns explicitly so the model has a first-line filter.
  • Untrusted-content markers — every text-shaped path a tenant can influence is wrapped with an explicit treat as data, not instructions boundary.
  • Tenant tool policy — per-tenant allow-destructive and require-confirm-destructive settings surface at /settings/agent.
  • Tenant content policy — blocked-phrase list, profanity filter, response-token cap, tool allow-list, all applied after every turn and before the assistant text is persisted; the per-turn log entry tags which rule fired without writing the un-redacted text.

Read the full security posture for how the perimeter composes with tenant isolation, RLS, and the per-row visibility gate.

BYOK every provider — per-step failover

The provider layer is portable. Anthropic, OpenAI, AWS Bedrock, Azure OpenAI, and Gemini all ship as full streaming adapters. Tenant settings expose a primary and a fallback. Per-step automatic failover fires when a provider is unavailable, rate-limited, or quota-exhausted — the SPA renders an inline “Switched from primary to fallback” badge and a fresh turn opens with the fallback model. Failover fires only before any visible content on the current step; once visible content lands, the active provider owns the rest of the step. Token counts stay clean and streams stay intact.

Tenant keys are encrypted at rest with the HKDF-derived LLM-provider-keys subsystem key. Logs carry the last-4 fingerprint of the key and nothing more.

Eight token-efficiency mechanisms

Token cost is engineered down at eight points: two-layer prompt caching (a stable generic system prompt + a per-tenant entity catalog, both ephemeral-cached), an in-prompt entity catalog so the agent skips orientation tool calls, deterministic tool-result compaction outside the most-recent window, an operator compact slash-command, per-step model routing (a reasoning-tier model for planning, a smaller model for dispatch), a conversation-size pre-flight guardrail that refuses turns projected over the per-minute cap, idle-aware resume tuned to the provider’s prompt-cache TTL, and a current-date preamble in the per-turn user message so the cached entity catalog stays valid across days.

Live rate-limit headroom sits under the composer — a continuous countdown of the provider’s per-minute input-token budget, amber under 16% remaining, red under 4%. Operators see budget pressure as it happens, not after the failure.

Latency by construction

Read-only tool-use blocks within one turn fan out concurrently under a bounded semaphore and stream results in completion order. Destructive calls stay sequential. A turn with ten read calls finishes in roughly the slowest call’s time — typically a 3–5x wall-clock win over sequential dispatch. Every tool call and its duration lands on the message record, so /platform/agent renders per-tenant, per-tool, per-entity p50 / p95 latency and a most-recent-50 drilldown. Full traffic, one observability pipeline, the same rows the agent already writes.

The takeaway

Actionary is agent-first from the substrate up. The business is data. The substrate generates the MCP tool surface. The execution layer lands what the agent proposes exactly once, no matter how many times the network retries. The agent inherits the caller’s permissions through the same code path as a browser session. The reflection memory is operator-inspectable. The safety perimeter has five composing layers. The LLM provider is a per-tenant configuration. Agent-first is the shape of the platform. For the sales-leader view of what this unlocks, see the CRO angle.