Inspiration

Most AI agents are amnesiac. They reason well about the request in front of them, but they don't know that a near-identical request was rejected last month, that the team already owns idle capacity, or that a temporary allocation expires tomorrow. Every decision starts from zero.

I wanted to build the opposite: an operations agent whose memory is the product, not an implementation detail — and one you could actually trust to take action, because the rules it enforces aren't just prompt text the model might forget.

What it does

Memento handles internal operational requests — GPUs, cloud instances, SaaS licenses, production services. Before deciding anything it retrieves both structured memory (who owns what, right now) and semantic memory (why past decisions went the way they did), then runs a deterministic policy engine that has final say over what the organization actually permits.

Two demo moments show the difference:

  • Ask for another GPU, and instead of approving, it notices the team already has two idle A100s and a third returning tomorrow — and asks whether existing capacity can cover it. Say "use the existing one" and it allocates that GPU and approves. No new spend.
  • Ask for a Grafana Cloud subscription, and it rejects: the org standardised on Datadog for observability after consolidating three vendors. Push back, and the policy still holds — because it's a hard rule, not a suggestion.

How I built it

Next.js (App Router) + TypeScript, CockroachDB for all persistent state, Amazon Bedrock for reasoning and embeddings, deployed on Vercel.

The architecture rests on one invariant: approving, rejecting, and escalating are not model-callable tools. The agent has 13 tools it can call, but the terminal one (record_decision) only proposes a decision. The runtime then reconciles that proposal against the deterministic verdict — and a hard policy always wins. A model that could call approve_request directly could route around the policy engine, and holding that line is the whole point.

The policy engine itself is eight pure TypeScript functions of a materialised context — no I/O, no model, no randomness. Same input, same verdict, every time. That's what makes it trustworthy enough to override a language model, and what makes it unit-testable (19 tests, no database or credentials required).

Memory is two complementary systems in one database: relational tables for exact facts, and an agent_memories table with a VECTOR(1024) column embedded via Titan Text Embeddings V2, retrieved by cosine distance and served by CockroachDB's distributed C-SPANN vector index. Every memory carries foreign keys back to the request or decision that produced it, so retrieval always traces to a real event rather than a free-floating string.

Challenges I ran into

Getting Bedrock to actually invoke anything was a multi-layer archaeology dig. The console Playground would happily load a model, then the API would 404. In order: model invocation requires a valid payment method on the AWS account (INVALID_PAYMENT_INSTRUMENT), independent of model access. The newest Claude generation is served via Bedrock's Mantle endpoint and requires AWS Sales approval on a self-service account — it returns permission_error no matter how many console buttons you click. And on-demand invocation needs the cross-region inference profile form of the model ID (us.anthropic.claude-...), not the bare ID that works fine in the console. All of it is now documented in docs/deployment.md so nobody has to rediscover it.

A subtle reconciliation bug that only appeared once real reasoning was running. With the deterministic fallback, the flow looked correct. But with Bedrock live, the model would correctly call reserve_existing_resource itself and propose APPROVE — and the runtime would override it back down to REQUEST_INFORMATION. The cause: the policy evaluation was a snapshot taken before the reuse happened, so its "reuse before you provision" verdict was stale by the time reconciliation ran. The fix was to fold the reuse outcome back into the evaluation regardless of which path performed it — the deterministic floor or the model's own tool call. A good reminder that a fallback path passing its tests tells you nothing about the primary path.

What I learned

That the interesting problem in agent design isn't getting the model to reason — it's deciding what the model is allowed to do with its reasoning. Separating "interpret this ambiguous request" (model) from "decide what's permitted" (deterministic code) made the system both more trustworthy and dramatically easier to test.

Also: graceful degradation is worth building early. Memento runs without Bedrock credentials — the policy engine still produces real, auditable decisions and memory falls back to a local lexical embedding, both clearly labelled as degraded in the UI and the database. That meant I always had a working demo while fighting AWS access, instead of a broken one.

What's next

Memory consolidation — periodically summarising many low-importance event memories into a single durable preference. Multi-agent delegation for research-heavy sub-requests. Slack intake, so requests arrive without anyone opening a dashboard.

Built With

Share this project:

Updates

Submission history