Decisions
ADR-0006: Trading agent and chat agent are separate processes sharing one Skill identity
- Status: accepted
- Date: 2026-05-27
- Refined by: ADR-0019 — the trading-vs-chat split stands (chat still has no order-placing tools), but chat is no longer a per-deployment agent that shares one Skill's identity. It is now one product-wide, user-scoped agent with its own brand persona, capable of authoring, coaching, and preparing actions across all the user's deployments.
Context
The deployer needs to ask their running agent natural-language questions about its decisions, risks, news context, etc. This is fundamentally different from the agent's trading loop:
| Property | Trading | Chat |
|---|---|---|
| Trigger | Schedule (cron/event) | Per deployer message |
| Side effects allowed | Place orders | None |
| Lifetime | Long-running (Fly machine) | Per-request (Vercel Function) |
| Latency sensitivity | Tick interval (seconds–minutes) | Conversational (sub-second tokens) |
| Concurrency | Strictly serial per Skill | Multiple sessions OK |
Putting both in one process means:
- Slow chat sessions can delay ticks (or vice versa)
- The same code path must handle both modes
- The chat agent inheriting
propose_orderis a real safety risk
Decision
Two separate agent instances per Skill, sharing the same Skill identity (system prompt, model, persona):
- Trading Agent — runs in the Fly live-runner process, on the Skill's schedule. Has
propose_orderand other write-capable tools. - Chat Agent — runs in a Vercel Function, per HTTP request. Has read-only tools + introspection tools. Cannot place orders.
Both call generateText/streamText with the same Skill system prompt (the chat agent's prompt has a short introspection addendum). Same Skill identity, different responsibilities.
Alternatives considered
Alt A — Single agent process handling both ticks and chat
- One identity, one code path
- Chat blocks ticks, ticks block chat
- Chat agent gets
propose_orderand could be prompt-injected into placing an order - Not picked: resource contention + safety regression. Worse on every dimension that matters.
Alt B — Chat as a non-agent service (templates over snapshots)
- Pre-template common questions ("why did you trade?" → render snapshot reasoning)
- Cheap, deterministic
- Loses the conversational flexibility users actually want
- Not picked: misses the product value. If the answer is a templated render, why bother with chat UX?
Alt C — One agent, but with mode-conditional tools
- Same process, but
modeparameter switches the tool whitelist - Still has resource contention (single process serving both)
- Still risks tool-list bugs leaking write tools to chat
- Not picked: minor benefit, real risk.
Consequences
Positive
- No resource contention — chat sessions can't delay ticks
- Safety boundary at the process level — the chat agent literally cannot place an order; its process doesn't have those tools loaded and its broker handle is
null - Independent scaling — chat is per-request HTTP, scales with web traffic; trading is per-Skill VM, scales with deployment count
- Cleaner cost model — chat costs attributed to chat usage; trading costs attributed to trading
- Different latency budgets — chat optimizes for first-token-fast (streaming); trading optimizes for tick-on-schedule (cron)
Negative / trade-offs
- Two code paths to maintain (mitigated: both use the same
packages/agent-runtimeprimitives and the samepackages/toolsregistry with different whitelists) - The chat agent doesn't have access to the trading agent's in-memory state — it reads everything from
agent_state+decision_snapshots. This means very fresh state (current tick in flight) may not yet be visible to chat. Acceptable. - Slight context-window inefficiency: each chat turn re-reads snapshots that the trading agent already has in memory. Cache-control + summary-first tools mitigate.
Things we'll need to revisit
- If we add conversational command ("flatten my position" via chat with confirmation), the architecture stays the same — the chat agent surfaces a confirmation UI, the user clicks, the web app inserts an
agent_commandsrow. Chat agent itself still has no command-issuing tool. - If we ever want the trading agent to remember conversations with the deployer (e.g., "I told you last week to be more conservative"), introduce a shared "deployer context" the trading agent reads. The split still holds.
References
docs/architecture/chat-agent.mddocs/architecture/live-runtime.md- ADR-0007 — imperative commands stay out of chat