hermes research Viska-autoresearch deployment arc · 4-parallel sub-agent · 2026-05-25

T4 — CC-Scheduled vs Nous-Agent Topology

Research date: 2026-05-25 · Track T4 of 4-track Viska autoresearch arc · Hermes (constellation-hermes) sub-agent · Topology recommendation for backend executor split

Executive recommendation

Stage 1: Pattern B — Supervisor. Plutus Claude Code scheduled sessions gate Nous-autonomous-agent outputs before they reach the Mastra/Slack surface, with a strict writer-namespaced KB schema borrowed from Pattern C as the persistence substrate. Equal-peer KB writes (Pattern A) are rejected because the failure modes of an unsupervised reasoning loop on trading data (hallucinated theses, prompt-injection-driven tool calls, infinite think loops) require a slower, higher-trust reviewer in band — and Plutus already exists as that reviewer with operator-visible session transcripts. Pure orthogonal task splits (Pattern C) leave the synthesis seam — combining ingested signals into actionable strategies — without a clear owner; that seam is where most of the value (and the risk) lives, so it cannot be the boundary between executors.

Task taxonomy

Six work classes; latency/state/autonomy/risk drives executor assignment.

Class What it does Latency tolerance Stateful? Autonomy appropriate Risk class
Ingestion Pull market data, news, fundamentals, social feeds, filings into raw KB. Seconds–minutes. Cron-driven. No (idempotent writes). High — pure pipeline. Low (read-only externally; injection vector on write).
Signal generation Nous-loop reasoning over ingested raw layer → candidate signals, anomaly flags, thesis seeds. Minutes–tens of minutes. Loop-local memory + KB append. High inside its sandbox. Medium — hallucination, drift, runaway think tokens.
Strategy synthesis Combine N signals + portfolio context + risk constraints → actionable strategy proposals. Tens of minutes – hours OK. Heavily — needs portfolio state, prior synthesis history. Low–medium. Operator-visible step. High — this is where bad signals turn into trade theses.
Validation / backtest Test signals/strategies against historical data. Compute risk metrics. Minutes–hours. Bursty CPU. Reproducible — pinned data + code. Medium — automatable but operator-reviewed. Medium — silent data-snooping bias.
Reporting Surface daily briefs, alerts, ad-hoc Q&A to Slack via Mastra. Sub-second (chat) – seconds (digest). KB-readonly. High — read-only synthesizer. Low — output-side only.
Governance Kill-switches, rate-limit caps, drawdown guards, sanity checks, audit log, prompt-injection scrubbers, model-version pinning. Real-time (kill) – minutes (audits). Tiny state (flags + counters). Should be deterministic, not LLM-judged. Critical — failures here are silent capability erosion.

Three properties drive assignment: (1) statefulness — signal-gen + synthesis carry running reasoning state suited to a long-lived loop; ingestion/reporting/governance are stateless cron-friendly. (2) Operator visibility — strategy synthesis must produce a re-readable transcript (Plutus CC's native strength via CC transcripts + chronicle); Nous-loop output is high-throughput, low-forensic-surface by design. (3) Failure containment — a misbehaving Nous must be cheaply detectable and stoppable without breaking ingestion or reporting.

Executor assignment matrix

Task class Nous agent (Railway) Plutus CC scheduled Mastra agent (Railway) Shared KB
Ingestion Owner (cron-scheduled scrape + load jobs) Read-only for ad-hoc Slack pulls Writes to raw/ namespace
Signal generation Owner (unsupervised loop) Reviewer (gate before promotion) Nous writes signals/candidate/*; Plutus promotes to signals/approved/*
Strategy synthesis Proposer (drafts in strategies/draft/*) Owner (gates promotion to strategies/approved/*) Read-only Two-stage namespace
Validation / backtest Triggers backtest job on draft Owner of backtest harness + verdict Writes results to backtests/
Reporting (Slack push + Q&A) Generates scheduled digests; writes to reports/ Owner of Slack-facing chat + on-demand Q&A over reports/ + strategies/approved/ Mastra is KB-readonly except for slack_threads/ audit log
Governance — kill-switch, rate limits, drawdown Owner (deterministic checks before approved-promotion) Exposes operator kill-switch Slack command governance/ flags table; all writers read before acting
Governance — audit log All writers append All writers append All writers append audit_log/ append-only
Governance — prompt-injection scrub Self-scrub on ingestion; Plutus re-scrubs at synthesis gate Owner of the scrub policy

Rationale: ingestion (deterministic + injection attack surface) → Plutus with cheap classifier-summarizer; Nous's reasoning model never sees raw HTML. Signals = Nous core competency but enter as candidates only. Strategy synthesis = highest-risk class, gating write stays with Plutus. Backtest harness = code, not LLM. Reporting = Mastra-native KB-readonly chat. Governance flags = deterministic kill-switches; Mastra exposes Slack command, flag table is SSOT every executor reads.

Pattern A — Peer

Shape. Nous and Plutus both write to shared KB with equal authority; writer-identity column + schema constraints handle conflicts at the data layer.

When it fits. Two writers with similar failure modes and similar operator-visibility profiles — e.g. two CC sessions on different repos writing to a shared status KB.

Failure modes for Viska. A misbehaving Nous loop (T1's three risks: hallucinated theses, injection-driven tool calls, infinite think loops) writes directly into state that Mastra and operators consume as live truth. Schema-level resolution can prevent literal contradictions ("BUY AAPL" vs "SHORT AAPL" 30 sec apart) but cannot express nuanced disagreement (where the right answer is "Plutus reviewed Nous's bear thesis, here's the gated version"). Operator pause is a write-blocker, not a review step; whatever Nous already wrote remains visible.

What it really requires. A workable peer pattern needs (a) writer-identity column on every row, (b) writer-domain partition so collisions are impossible, (c) versioned writes with provenance, (d) reconciliation jobs. By the time all four exist, the system is Supervisor with extra steps.

Verdict. Reject. The trust asymmetry between an operator-visible Plutus CC transcript and an unsupervised Nous loop is too large to flatten. The operator's mental model — "Plutus reviewed and signed off" — must map directly to a KB state change.

Pattern B — Supervisor

Shape. Nous writes only to staging (signals/candidate/*, strategies/draft/*, backtests/requested/*). Plutus CC sessions poll those namespaces on a schedule, review with full operator-visible transcript, and promote acceptable artifacts to */approved/* via an explicit write carrying reviewed_by, review_session_id, verdict_excerpt. Mastra reads only approved namespaces. Promotion is the single trust gate.

Latency cost. Adds Plutus's poll interval (5–15 min Stage 1, tighter during market hours) between Nous output and operator-visible state. This is the correct latency profile — research strategies should not surface to Slack faster than they can be reviewed, and 5–15 min sits well inside any decision timescale for synthesis (Stage 1 has no execution loop).

Why it's load-bearing Stage 1. (a) Client positions + PII + unpublished IP in KB — Nous outputs need sanity gate before Slack. (b) First time unsupervised loop touches client data; supervisor gate forces operators to read Nous output and measure Plutus's catch rate, which is itself the Stage-2-relaxation calibration artifact. (c) Misbehaving Nous fills only */candidate/* — Mastra sees nothing, pause = revoke Nous KB key, approved untouched. (d) Promotion writes give the audit log a clean "Plutus reviewed N, approved K, verdict excerpts" daily summary.

Costs. Plutus CC review tokens (sub-$100/mo at Stage-1 volume on top of T1's $23–55/mo Nous spend). Two-stage namespace = more schema. Plutus can become a queue bottleneck — mitigated by Plutus owning the Nous rate-limit governor.

Pattern C — Orthogonal

Shape. Strict domain split. Nous owns signals/*. Plutus owns strategies/*. Mastra owns reports/* + slack_threads/*. No overlap; CRUD on own namespace, read-only on others.

When it fits. Genuinely independent task classes with no synthesis seam — e.g. anomaly detection vs portfolio rebalancing as fully separable products.

Why it fails for Viska. The synthesis seam (signals → strategies) is the entire point of the autoresearch loop. If Nous owns signals and Plutus owns strategies with no shared owner of the seam, then either Plutus reads Nous's signals and writes strategies (which is Pattern B by another name) or Nous writes naive strategies and Plutus rewrites them (which is Pattern A). The seam cannot be the boundary between executors; it must live inside one executor or be gated between them.

Where Orthogonal contributes. Schema-level writer-namespacing is the substrate for Pattern B's gate. Adopt the discipline (writer-namespaced tables, read-only cross-writer access) inside Pattern B; do not adopt it as the top-level topology.

Verdict. Reject as top-level, adopt as substrate.

Pattern B — Supervisor — with Pattern C's writer-namespaced schema as substrate. Plutus CC scheduled sessions are the only writer to any namespace Mastra reads; Nous writes only to staging; promotion is a Plutus row carrying review provenance. Governance flags are deterministic, read by every writer before every write.

Against Pattern A. Pattern A flattens trust asymmetry that genuinely exists. Plutus CC sessions are operator-visible CC transcripts with chronicle + devlog entries — they are the artifact operators read. Nous-loop output is high-throughput and low-forensic-surface by design. Peer-writing leaves the KB row ambiguous (is signals/AAPL/buy a Nous hallucination or a Plutus-vetted thesis?); writer-identity columns technically answer that but push interpretation onto every reader (Slack consumers, dashboards, future agents). Pattern B encodes the interpretation in the write itself.

Against Pattern C. Pure orthogonal orphans the synthesis seam. Karpathy's auto-research pattern is fundamentally about compounding — raw → signals → strategies → reports — and compounding requires an owner of the upgrade transition. The supervisor is that owner. Without one, you re-invent A or B by accident.

Stage-1 data sensitivity. First time an unsupervised loop reasons over client positions + PII + unpublished IP. Cost of one bad signal reaching Slack (operator acts on a hallucination, or worse, end-client sees it via the bot) dwarfs Plutus review-cycle cost. Stage 2 can relax once catch-rate data exists.

Operator visibility. Plutus has STATE.md, ACTIVE-SESSION.md, chronicle, devlog — surfaces operators already read. Nous loops do not. Pattern B routes every consequential state change through the operator-visible surface; A and C both bypass it.

Failure containment. Pattern B contains blast radius to staging namespace. Pattern A requires post-hoc reconciliation across the approved KB. Pattern C requires deciding whose failure stops whom.

Cost. Pattern B marginal cost ≈ Plutus review tokens, sub-$100/mo at Stage-1 volumes on top of T1's $23–55/mo. Pattern A's hidden cost = reconciliation engineering in every consumer. Pattern C's hidden cost = duplicated synthesis work.

Dispatch protocol

Nous-loop trigger. Primary = cron (15 min during market hours, hourly off-hours). Each tick must claim a nous_run_queue/* row (Plutus-filed targeted requests, or operator-filed from Slack via Mastra) or a flagged raw/news/* row. No queue, no loop — cheapest cycle-detector. Token-budget guard per tick caps think-trace length.

Plutus consumes Nous output. CC session wakes on own cron (5–15 min). First action: read signals/candidate/* + strategies/draft/* where reviewed_at IS NULL AND created_at > now() - 24h. Per candidate: read linked nous_traces/* reasoning trace, evaluate against checklist (sanity + risk + provenance + injection-scrub), write promotion row to */approved/* with reviewed_by, review_session_id, verdict_excerpt — or rejection row with reason. Append-only across the board; Plutus never mutates Nous's rows.

Mastra discovers new data. Supabase realtime subscription on */approved/* + reports/*. New approved rows fire a synthesizer that may push to Slack (digests, severity=high signals). 60-sec polling fallback if realtime drops. Mastra is KB-readonly except slack_threads/* (own audit log) and nous_run_queue/* (forwards operator-filed Slack research requests as queue rows).

Kill-switch / pause. Soft: Slack command /viska pause nous → Mastra writes governance/flags/nous_active=false. Every writer reads this flag before any write; false → log skip. Same for plutus. Hard kill: Hades revokes the writer's KB credential — next tick fails with a clear auth error, zero partial state. Auto-pause: governance daemon flips the flag when thresholds breach (e.g. >N rejections/hr → pause Nous, notify operator).

Failure-mode handoffs

Failure Detector Responder
Nous produces gibberish signals Plutus review (per-candidate); secondary = Plutus heuristic counter (rejection rate >50%/hr) Plutus writes governance/incidents/* + Mastra realtime push to operator; operator may flip nous_active
Nous loop infinite-reasoning cycle Nous-side token-budget guard per tick; Plutus cron-watchdog if nous_traces/* for a queue row exceeds wall-clock without completion Watchdog cancels loop (KB cancel-row + provider API cancel). Hermes-4 tool-call format parseable mid-stream (T1) for liveness check
Drawdown / risk-limit breach Plutus deterministic pre-approval risk-check (never LLM-judged) Refuse promotion; write governance/incidents/risk_breach_*; Mastra alert
Transient KB write failure Per-writer retry-with-backoff After N retries, local-file persist + ntfy out-of-band alert; replay on recovery
Mastra outage Plutus healthcheck cron Plutus keeps writing reports/*; queue drains via realtime subscription on recovery. KB authoritative, Slack is a view
Prompt injection on Nous tool call First: ingestion-classifier-summarizer (Plutus, cheap model); second: Plutus pre-promotion injection-scrub Reject + governance/incidents/injection_*; auto-pause Nous if same source domain produces 3+ scrubbed candidates / 24h
Plutus review queue backup Plutus writes governance/metrics/plutus_queue_depth per cycle Auto-throttle Nous via governance/flags/nous_rate_limit (e.g. cron interval doubles); operator notified
Silent model-version drift (T1) Plutus checks response-header model id per Nous trace Mismatch with pinned id → reject candidate + alert

Topology diagram description

Spec for Excalidraw render by coordinator.

Nodes. - OPERATOR (top-left, circle): configures, reviews, kills. - SLACK (top-center, pill): operator + end-client interaction surface. - MASTRA_AGENT (Railway CPU) (upper-mid, rectangle): Slack-facing chat + report serving. KB-readonly except own audit log. - NOUS_AGENT (Railway CPU + Nous Forge API) (mid-left, dashed border = autonomous): signal generation, strategy draft. Staging writes only. - PLUTUS_CC (scheduled sessions) (mid-center, double border = trust anchor): ingestion, review, promotion, backtest, report digest. Only writer to approved namespaces. - KB (client-isolated Supabase) (bottom, large rounded rect with horizontal swimlanes): raw/, signals/{candidate,approved,rejected}/, strategies/{draft,approved,rejected}/, backtests/, reports/, governance/{flags,incidents,metrics}/, nous_run_queue/, nous_traces/, audit_log/, slack_threads/. - INGESTION_JOBS (far-left, small rect, Plutus-owned cron): feeds raw/. - GOVERNANCE_DAEMON (far-right, small rect, Plutus-owned): reads metrics, writes flags. - HADES (top-right, key icon, out-of-band plane): credential rotation/kill.

Edges (direction · payload). - OPERATOR → SLACK (chat). - SLACK ↔ MASTRA (Slack events/replies). - MASTRA → KB (slack_threads/, nous_run_queue/) (write). - MASTRA ← KB (reports/*, *_approved/*) (realtime subscription). - INGESTION_JOBS → KB (raw/*) (cron write). - NOUS ← KB (raw/*, nous_run_queue/*) (read). - NOUS → Nous Forge API (HTTPS, OpenAI-compat). - NOUS → KB (signals/candidate/*, strategies/draft/*, nous_traces/*, backtests/requested/*) (staging write). - PLUTUS ← KB (candidate/draft/traces/raw) (cron read). - PLUTUS → KB (*_approved/*, *_rejected/*, backtests/*, reports/*, audit_log/*) (append-only). - GOVERNANCE_DAEMON ↔ KB (governance/*). - All writers → KB (audit_log/) (append-only provenance). - HADES ⇢ {NOUS, PLUTUS, MASTRA, INGESTION} (dotted, credential plane).

Trust boundary boxes (dashed). A: autonomous (Nous + staging). B: supervised (Plutus + approved + governance). C: external surface (Slack + Mastra, KB-readonly). D: credential plane (Hades).

Failure-detection arrows (thin, dashed, red). Plutus ⇢ Governance (rejection metrics) · Governance ⇢ Nous (flag-mediated auto-pause) · Mastra ⇢ Operator (incident realtime push) · Plutus ⇢ Mastra via KB (watchdog incidents).

Open questions for operator

Cross-refs