engineering
Durable Agent Workflows Need More Than a Model
Why Mowazi assigns different responsibilities to Convex, Temporal, Timescale, Redis, and the execution boundary.

An agentic product is not a chatbot connected to a wallet. It is a collection of systems that need different guarantees.
Mowazi assigns those guarantees deliberately. Convex holds authenticated product state and current UI snapshots. Temporal coordinates durable workflows. Timescale stores detailed evidence and history. Redis holds ephemeral coordination, reservations, and runtime controls. Isolated execution services handle venue access and deterministic checks.
One source of truth per kind of truth
This structure avoids a common failure mode: copying the same state into several systems and then asking an operator to decide which copy is correct. Current user-facing status belongs to the product state layer. Detailed traces and historical evidence belong to the history layer. Short-lived coordination does not masquerade as a permanent record.
Idempotency is part of safety
Financial workflows encounter retries, partial failures, delayed responses, and uncertain submissions. Mowazi preserves idempotency keys through those paths so a retry is not treated as permission to repeat a consequential operation.
Durability also matters for cost and observability. Workflow state should survive a process restart; trace data should support inspection without exposing private reasoning; background work should not create idle traffic simply because an application is waiting for something to happen.
The result is less theatrical than an "autonomous agent" demo. It is more useful: a system whose responsibilities can be inspected before it is asked to carry more authority.
The implementation is a set of owners, not one backend
Mowazi's current architecture names a different owner for each type of fact. Convex owns authenticated product state: user and strategy relationships, policy versions, proposals, approvals, venue readiness, credits, and current UI snapshots. Timescale/Postgres owns sanitized evidence, detailed model reports, provider traces, evaluations, and historical artifacts. Temporal owns durable schedules and workflow state. Redis owns expiring controls, locks, reservations, hot evidence, and short routing caches.
That division is more than an infrastructure preference. It means a Redis key cannot clear a durable incident, a Convex snapshot cannot replace detailed evidence history, and a provider response cannot be treated as product success when the current-state projection failed.
01 / current
Convex
Authenticated product state, proposals, approvals, and UI snapshots.
02 / durable
Temporal + Timescale
Workflow coordination, immutable evidence, and detailed history.
03 / hot
Redis + workers
Expiring coordination, capped runtime state, and bounded analysis.
The execution gateway and the TypeScript sidecar form another boundary. They own current quotes, deterministic preflight, limited venue interaction, and preparation. They do not own the model's reasoning, the browser session, or a general-purpose product database. This keeps high-consequence capability from migrating toward the most convenient process.
A workflow begins with an idempotent product decision
Retries are normal in a distributed system. They can arise from a worker crash, a network timeout, a delayed venue response, or a Temporal retry. The safe question is not “did the request happen?” but “does this retry represent the same bounded operation?”
The current execution workflow carries an idempotency key from the proposal into preflight, dispatch, and reconciliation:
# Representative pattern from the execution workflow.
idempotency_key = proposal_row["idempotencyKey"]
execution = await gateway_call("/internal/dispatch", {
"venue": venue,
"request": proposal.model_dump(mode="json"),
"decision": decision,
"idempotency_key": idempotency_key,
"safety_subject_key": f"proposal:{proposal_row['_id']}",
})
reconcile_key = f"{idempotency_key}:reconcile"
The extra reconciliation suffix is intentional. Reconciliation is related to the original operation but is not the original dispatch. The system can make a durable statement about what it observed without repeating the submit attempt.
Current snapshot versus durable history
The product needs a responsive view of what is happening now. It also needs a durable account of how that state came to be. Mowazi's snapshot rule keeps one current Convex row per logical scope, such as a public market or a private strategy-and-market pair. Detailed history remains outside that projection.
// The product-facing shape is intentionally small and replaceable.
type CurrentSnapshot = {
scope: "public_market" | "strategy_market";
forces: unknown;
story: unknown;
scenarios: unknown;
agents: unknown;
galaxy: unknown;
updatedAt: number;
};
// Historical evidence belongs in the history system, not this UI row.
This split is why the UI can update without turning Convex into a long-term trace warehouse. It is also why a worker should write detailed historical artifacts before it patches the current projection. If the view updates but the durable record did not, the operation is incomplete and should not be narrated as a clean success.
Redis is coordination, never authority
The implementation uses Redis for bounded tasks: account/market overlap locks, daily caps, short quote previews, trigger coalescing, provider reservations, and live trace pub/sub. Those keys have TTLs. They are allowed to expire, be lost, or be rebuilt. They cannot be the only record of a material customer decision or execution transition.
// A cache may accelerate a preview. It must never grant permission.
const preview = await redis.get(`routing:preview:v1:${requestDigest}`);
if (preview) return JSON.parse(preview);
const fresh = await gateway.quote(request);
await redis.setex(cacheKey, 10, JSON.stringify(fresh));
return fresh;
The production rule is stricter than the example: even a fresh-enough preview cannot replace the execution-time recheck of approval, quote freshness, balance, policy, and health.
Temporal prevents the polling-shaped architecture
The architecture's zero-idle-traffic rule is a practical consequence of durable orchestration. Workers wait on Temporal task queues; schedules can be paused by Manual Guard in development; the old once-per-second job claimer is not part of normal startup. This prevents an idle system from continuously touching Convex simply to discover that nothing happened.
Durability is therefore not synonymous with constant activity. A workflow is durable when it can wait, retry, resume, and leave inspectable state without converting absence of work into background noise.
A bounded claim about the current product
This architecture supports Mowazi's Closed Beta posture. It does not mean every venue adapter has a live signing path. Current documentation distinguishes public read-only data and preparation from certified private execution, and it deliberately blocks generic broadcast routes until the required controls are complete. The workflow design exists to make those incomplete edges visible rather than hide them behind an autonomous label.