5-Step TL;DR for CEO
The minimum you need to get a portfolio recommendation in under 10 minutes.
- Check the gate — open portal and confirm
ARMED · real_moneybadge is green. Or:GET /status→effective_armed: true. - Fetch your TR portfolio — run
python engine/tr_client.pyon your local machine, approve the push notification in the TR app. See your current positions and cash. - Score candidates —
GET /candidates/scorereturns every candidate ranked by V (velocity). Candidates failing a hard gate showeligible: false. - Run a round —
POST /askwith your question and the top-scoring tickers. The 20-role council deliberates in 2–5 minutes. A transcript and recommendation appear in the portal. - Decide and act — read the transcript (especially the Devil's Advocate dissent).
POST /decisions/{round_id}/approveor/rejectwith a note. Execute manually in the TR app. Never hold through the binary.
POST /gate/authorize → CAB approval → POST /gate/arm.
System Overview
1.1 What is the Decision Machine?
The Decision Machine is a governed AI council that produces structured buy / hold / pass recommendations for small- and mid-cap biotech candidates approaching a binary catalyst. It is not an autonomous trading system. You bring research data; the machine scores, debates, and returns a written decision surface; you review and act.
The edge it exploits: public-information mispricing in under-analysed biotech. Position early in a catalyst path, exit 2–4 weeks before the binary readout — never hold through the binary.
1.2 The AIM Grid — 3-Column Architecture
Every part of the system is owned by exactly one column. No column improvises another column's content.
Business (WHAT)
Owns the edge thesis, scoring formula, enrichment cadence, information categories, and the hard rule that math lives only as data — never in role prompts.
Information (HOW-GOVERNS)
Owns the change process, the isolation principle, lane routing, the CAB lifecycle, and the G9 real-money quorum rule. Governs changes to Business and Technology rules.
Technology (HOW-RUNS)
Owns the infrastructure, the board (Postgres), the score engine, the API, and the web interfaces. Computes; never owns the rules. Reads Business rules as data.
1.3 Core Principles
- Model wins over chat: everything decided goes into the model before moving on. Nothing is built from memory or conversation.
- Top-down discipline: start at top-level processes, go deeper only when needed.
- Rules as data: trading math lives in
scoring_config.yaml(Business-owned). The score engine reads it; no role embeds math in its prompt. - Fail-closed: any missing precondition keeps
effective_armed=false. Unreadable state returns HOLD. No silent success. - Append-only: the board (
messagestable), the ledger, and all audit logs are never updated or deleted. An existing row is the audit entry. - Separation boundary: the running engine container never touches the host deploy checkout, never pushes to git, never runs
docker compose.
The AI Council
2.1 All 20 Roles
| # | Role | Type | Model | Pinned | Mode | Function |
|---|---|---|---|---|---|---|
| 1 | analyst_desk | Sonar | deep-research | free | manual | Deep-research synthesis on candidate |
| 2 | risk_review | Sonar | sonar-reasoning-pro | free | manual | Probability & downside risk assessment |
| 3 | portfolio_manager | Sonar | pro-search | free | manual | Position sizing & portfolio fit |
| 4 | perplexity_expert | Sonar | pro-search | free | auto | Real-time web research on news / catalyst |
| 5 | app_support | Sonar | fast-search | free | auto | Data lookup & factual verification |
| 6 | coordinator | Agent | openai/gpt-5.2 | pinned | manual | Runs rounds, assembles transcript & decision surface |
| 7 | change_manager | Agent | openai/gpt-5.4 | pinned | manual | Governs change lifecycle (Information column) |
| 8 | overall_process_owner | Agent | openai/gpt-5.4 | pinned | manual | Cross-process integrity & G9 CAB quorum |
| 9 | business_process_owner | Agent | anthropic/claude-sonnet-4-6 | pinned | manual | Business rule adherence |
| 10 | cio | Agent | openai/gpt-5.4 | pinned | manual | Investment thesis challenge (Information column) |
| 11 | cto | Agent | openai/gpt-5.5 + web_search | pinned | manual | Technology & data quality review |
| 12 | devils_advocate | Agent | anthropic/claude-opus-4-8 | pinned | manual | Structured counter-argument on every thesis |
| 13 | product_owner | Agent | anthropic/claude-sonnet-4-6 | pinned | manual | Output quality & scope control |
| 14 | it_architect | Agent | openai/gpt-5.4 | pinned | manual | Infrastructure & integration concerns |
| 15 | solution_architect_it_pm | Agent | openai/gpt-5.4 | pinned | manual | End-to-end solution coherence |
| 16 | quality_manager_auditor | Agent | openai/gpt-5.2 | pinned | manual | Round quality check & audit trail |
| 17 | developer | Agent | anthropic/claude-opus-4-8 | pinned | manual | Tooling & code concerns |
| 18 | sysadmin_devops | Agent | openai/gpt-5.4 | pinned | manual | Operational & deployment concerns |
| 19 | trade_expert | Agent | openai/gpt-5.4-nano | pinned | manual | Biotech trading mechanics & exit feasibility |
| 20 | compliance | Agent | openai/gpt-5.4-nano | pinned | manual | Regulatory & governance checks |
You (CEO) are not a role. You are the final decision-maker who reads the transcript and acts.
2.2 Sonar Roles (5)
Sonar roles call the Perplexity Sonar API (api.perplexity.ai). They are grounded — they search the web in real time and cite sources. They are used for research synthesis, risk assessment, and factual data lookup. They do not hold calculations.
- analyst_desk — writes the candidate brief: thesis, trial design, prior data, dilution risk, price action.
- risk_review — estimates p (probability of re-rating), downside scenarios, tail risks.
- portfolio_manager — assesses fit with current exposure, sizing, correlation to existing positions.
- perplexity_expert — real-time news, recent filings, catalyst calendar, sentiment signals.
- app_support — data lookup, fact-checking, change-process front door, known-problem register.
2.3 Agent Roles (15)
Agent roles call the Perplexity Agent API. They receive tool access (e.g. web_search for cto) and perform structured reasoning tasks. Pinned roles fail startup validation if their model ID is absent from the live catalog.
2.4 The Isolation Principle
Each role reaches its judgement in isolation — it sees only its own prompt and the handover protocol relevant to its task. It does not see another role's private reasoning. This prevents anchoring: independent views aggregated produce a better council than one view echoed back.
2.5 Round Execution — Step by Step
Business Rules
3.1 The Edge Thesis (B1 — locked)
Public-information assembly in under-analysed small/mid-cap biotech: position early in a mispriced catalyst path, then exit 2–4 weeks before the binary readout — never hold through the binary.
Evidence base: ~+14% run-up in the ~120 days before a Phase-3 readout; "buy the rumour, sell the news." The edge is in the asymmetry between public information assembled systematically and the market's inattention to small-cap catalysts.
3.2 Q Score — Quality [0–100]
Q is computed by score_engine.py reading scoring_config.yaml. No role computes Q. Roles hand over the input pieces; the tool does the math.
| Bucket | Weight | Input field(s) | Notes |
|---|---|---|---|
| Adjusted PoS | 25% | adjusted_pos_score | 0–100 probability the catalyst path re-rates upward |
| Expected Magnitude | 25% | expected_magnitude_score | 0–100 size of the upside move if re-rating occurs |
| Catalyst Timing | 25% | days_to_catalyst, date_confidence | confidence multiplier: high=1.0 / medium=0.7 / low=0.4 |
| Edge Intactness | 15% | edge_intactness_base, x_signal, x_crowded | X-signal sub-scores: mention_velocity_z (35%), quality_weighted_sentiment (30%), smart_money_ratio (20%), early_vs_retail (15%) |
| Financial Runway | 10% | financial_runway_score | 0–100 confidence company survives past catalyst |
Q-weight multiplier: q_weight = 0.5 + 1.0 × (Q/100) → range [0.5, 1.5]. Low-Q candidates are penalised in the V ranking.
q_min = 50: candidates with Q < 50 are parked (not ranked, not shown to the council) pending data enrichment.
3.3 V Score — Velocity
V = (p × R_up − (1−p) × R_down) / T_exit × q_weight
| Variable | Meaning | Source |
|---|---|---|
p | Probability the path re-rates before our exit | role hands over; stored in candidate record |
R_up | Expected upside return if re-rating occurs (fraction) | role hands over |
R_down | Expected downside if thesis breaks (fraction) | role hands over; default by market-cap band if UNKNOWN |
T_exit | Days to our exit (days_to_catalyst − 14 to 28 days) | computed from days_to_catalyst |
q_weight | Q-based multiplier [0.5–1.5] | computed from Q |
R_down defaults by market-cap band (used only when R_down = UNKNOWN): micro = 25%, small = 20%, mid = 15%.
3.4 Five Hard Gates (auto-reject)
Any candidate failing a hard gate is rejected before scoring and before the council sees it. An UNKNOWN input makes the gate unevaluable → candidate is parked, not rejected.
| Gate | Field | Rule | Rationale |
|---|---|---|---|
| 1. Cash runway | runway_months_past_catalyst | ≥ 18 months | Company must survive past the catalyst |
| 2. Lead asset concentration | lead_asset_pct_of_value | ≤ 50% | Avoid single-asset binary-outcome names |
| 3. Market cap band | market_cap_band | micro / small / mid only | Edge is in under-analysed caps; large = efficient |
| 4. Clean exit | clean_exit_feasible | = true | Must be able to exit 2–4 weeks before binary |
| 5. Liquidity | liquidity_exitable | = true | Position size must be exitable vs daily volume |
| + Ruin risk | ruin_risk | = false | Automatic reject on any ruin-risk flag |
3.5 Enrichment Tiering (B3)
V decides who gets fresh data, controlling machine load:
- Top 10 by V → enrichment every trading day
- Next 40 → enrichment weekly
- Rest → enrichment monthly
- Cold-start guard: a new discovery gets ≥X full enrichment runs before competing on cadence
/T_exitalready lifts near-catalyst names naturally to the top tier
3.6 Five Information Categories (B4)
Gatherers must cover all five. The minimum daily set (required before a real-money round) is starred:
| Category | Key fields | Min daily? |
|---|---|---|
| 1. Catalyst / Timeline | days_to_catalyst, date_confidence | ★ Yes |
| 2. Clinical / Scientific | trial design, prior data, mechanism | No (weekly) |
| 3. Financial / Dilution | cash_runway, shelf_flag, share-count growth, CEO dilution language | ★ cash_runway + shelf_flag |
| 4. Market / Price | price, trend, volume | ★ Yes |
| 5. Environment / Sector | IBB trend | ★ Yes |
Data freshness rules (real-money mode): catalyst + price fields max 1 trading day old. Cash runway and shelf flag max 7 days old.
Information Column — Change Management
4.1 Overview
The Information column governs how the machine changes itself. Every change to a Business rule, a role prompt, a scoring parameter, or a technology principle routes through a structured lifecycle. Nothing is tweaked live. No informal edits bypass the record.
4.2 Lane Routing — 4 Lanes
| Lane | Name | When | CAB | Observation window |
|---|---|---|---|---|
| 1 | Emergency | Life-safety / regulatory crisis | Retrospective (post-action) | No |
| 2 | Pre-authorized | Scope pre-approved by governance | Not required | No |
| 3 | App Support | Operational / support-attested | Scoped | Optional |
| 4 | Standard | Everything else — default | Full CAB (calculation changes: full lifecycle + observation window) | Required for calculation changes |
When in doubt → Lane 4. Calculation changes (scoring weights, gates, formula, parameters) always get Lane 4 with full CAB and a deferred effectiveness re-check after an observation window.
4.3 Change Lifecycle (9 States)
| State | Who acts | API call | Notes |
|---|---|---|---|
| INTAKE | Submitter | POST /change-request | Records problem, submitter, impacted columns, triage hint |
| TRIAGE | App Support (AI) | POST /change-request/{ref}/run-phase {to_phase: triage} | AI classifies severity, urgency, lane |
| SOLUTION | Impacted roles (AI, isolated) | run-phase solution | Each role gives its solution in isolation |
| AGGREGATE | Change Manager (AI) | run-phase aggregate | Synthesizes complementary pieces; carries shortlist where options genuinely rival |
| COST_BENEFIT | Change Manager (AI) | run-phase cost_benefit | Credits consumed vs credits saved; cost vs benefit; human Perplexity credits input |
| CAB | Human (CEO chairs) | advance to_phase: cab_go or cab_object | Worth-it verdict is the human's to give, not the Change Manager's |
| QUALITY | SysAdmin (AI) + reality gate | run-tests PARTIAL; execute-plan; verify-and-close | Reality gate must pass before LIVE |
| LIVE | System | auto on verify-and-close pass | Bundle workarounds retired; findings resolved |
| CLOSED | System | auto after LIVE | Terminal state for successfully shipped changes |
4.4 CAB Process
The Change Advisory Board is the human governance gate before a change goes live. The CEO chairs. The CAB does not have delegated authority to the machine — the worth-it verdict is always human.
- For calculation changes: full CAB attendance + observation window after go-live before the change is considered confirmed
- For instruction changes: scoped CAB attendance (impacted roles' owners only)
- CAB GO → advances to QUALITY; CAB OBJECT → REJECTED
- CAB record reference (
cab_record_ref) is required for gate authorization approval
4.5 Real-Money Gate Authorization (G9)
G9 CAB Quorum Rule (G9-QUORUM-001)
| Role | Requirement |
|---|---|
| Chair — CEO (Eric Trapman) | Must be present; cannot be delegated |
| Change Manager (AI) | Present or formally delegated |
| Quality Manager / Auditor (AI) | Present or formally delegated |
| Overall Process Owner (AI) | Present or formally delegated |
| Perplexity Expert (AI) | Present or formally delegated |
| Impacted cell representative (AI) | Present or formally delegated |
The "Quorum rule applied" field in Artifact M must read: G9-QUORUM-001 — never blank.
4.6 Submitting a Change Request — Step by Step
This section explains exactly what you do, in what order, and who (human or AI) acts at each step. By the end the change is live and verified.
API (full control): use the curl commands below. Every portal button calls the same endpoints, so both paths are equivalent.
Before you start — two questions to answer
- What column does this touch? Business (trading rules, scoring, edge thesis) · Information (change process, governance) · Technology (code, UI, API, config). Most portal features are Technology. When in doubt, pick Technology.
- Is this an emergency? If the system is broken and live money is at risk, use Lane 1 (Emergency) — submit the CR and act immediately, CAB is retrospective. For anything else, follow the standard flow below.
The full flow at a glance
-
Submit the change request — describe the problem or feature in plain language. You do not need to specify a solution; the AI roles produce that in later phases.
Cockpit: open /cockpit.html → "New Change Request" form → fill in description and column → Submit.
API:
The response containscurl -X POST https://engine.accommodus.com/change-request \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{ "problem": "I want a dedicated business operations page in the portal.", "submitter": "eric-trapman-ceo", "impacted_columns": ["Technology"] }'"request_nr": "CHG-NNN". Note this — it is the reference for every subsequent call. -
Run Triage — the app_support AI reads your description and classifies lane, severity, and urgency. This is automatic; you do not write the triage, you only trigger it.
Check the result: confirm the lane. Most portal/code changes will be classified as Lane 4 — Standard. If the lane is wrong, you can reclassify viacurl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-phase \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "triage"}'POST /change-request/CHG-NNN/lane/reclassify. -
Run Solution — each impacted role contributes its perspective in isolation. For a Technology change, the developer, it_architect, solution_architect_it_pm, sysadmin_devops, and others each produce their part of the solution without seeing each other's reasoning.
You do not need to read or approve the solution output — it feeds the next phase automatically.curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-phase \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "solution"}' -
Run Aggregate — the change_manager synthesises all role solutions into one coherent implementation plan. If roles disagreed, the aggregate carries the shortlist.
curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-phase \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "aggregate"}' -
Run Cost/Benefit — the change_manager estimates credits consumed vs saved and surfaces the cost case.
curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-phase \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "cost_benefit"}' -
CAB review — your decision — this is the only step where you must read the output and make a judgment. Open the CR detail:
Or open /cockpit.html and click the CR to read the full aggregated plan and cost-benefit summary.curl https://engine.accommodus.com/change-request/CHG-NNN \ -H "X-Board-Token: <token>"
If you approve (CAB GO):
If you reject (CAB OBJECT):curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/advance \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "cab_go", "actor": "eric-trapman-ceo", "note": "Approved."}'
A CAB OBJECT moves the CR to REJECTED — this is non-terminal (FND-036): changes.html shows a "Re-route to Solution" button on a Rejected CR, which (as of CHG-043) triggers a real Solution-round AI consult, not just a phase-pointer move. The record remains referenceable as evidence of the original considered decision.-d '{"to_phase": "cab_object", "actor": "eric-trapman-ceo", "note": "Reason here."}' -
Run acceptance tests — after CAB GO the sysadmin_devops role runs a read-only acceptance test pass to verify the preconditions for execution are met.
curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-tests \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"tier": "PARTIAL"}'PARTIALis the default and sufficient for most changes.FULLruns a deeper suite and spends more API credits. -
Execute — the developer AI authors the patch — the developer role (claude-opus-4-8) reads the approved aggregate and the current repo files, then authors a structured patch (file paths + full contents). The patch is validated and persisted in the board. The container does not write to disk — that happens on the host in the next step.
Cockpit / changes.html (recommended): find the CR in the list → click it → click Execute (AI — ~90s) in the NEXT STEPS panel. Wait for thePATCH READYboard row to appear.
API:curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/run-phase \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"to_phase": "execute"}'If Execute returns HELD: the developer blocked — read the reason in the timeline. The most common cause is a required file being >64 KB and truncated in the AI's context. See §7.6 for the full mechanism and troubleshooting. -
Host apply — land the patch on the server — SSH to the server and run the host applier. Replace
CHG-NNNwith the actual reference (e.g.CHG-005):
This fetches the persisted patch from the board, writes the files, commits ascd /root/dm-engine ./engine/host/apply_authored_patch.sh CHG-NNNdm-developer-role, rebuilds the engine, and pushes to origin. The/statusendpoint will show the new commit when done. See §7.6 for full details and flags. -
Verify and close — the reality gate — back in changes.html, advance the CR to Quality Gate and click Run Quality Gate. Or via API:
If the gate passes all probes, the CR advances to LIVE → CLOSED automatically. If it returns HOLD, read the failing probe name and reason, fix the underlying issue, and re-run.curl -X POST https://engine.accommodus.com/change-request/CHG-NNN/verify-and-close \ -H "X-Board-Token: <token>" \ -H "Content-Type: application/json" \ -d '{"tier": "PARTIAL", "actor": "sysadmin_devops"}'
Summary: who does what
| Step | Actor | What they do |
|---|---|---|
| Submit | CEO (you) | Describe the problem in plain language — no solution required |
| Triage | app_support (AI) | Classifies lane, severity, urgency |
| Solution | All impacted roles (AI, isolated) | Each role proposes its piece of the solution independently |
| Aggregate | change_manager (AI) | Synthesises role solutions into one plan |
| Cost/Benefit | change_manager (AI) | Computes credits consumed vs value delivered |
| CAB | CEO (you) | Read the plan and cost — GO or OBJECT. This is your governance gate. |
| Tests | sysadmin_devops (AI) | Runs read-only acceptance checks |
| Execute | developer (AI — claude-opus-4-8) | Authors a structured patch and persists it in the board. Does not write files or deploy — separation boundary. |
| Host apply | CEO (you) | SSH → apply_authored_patch.sh CHG-NNN: writes files, commits, rebuilds, pushes to origin |
| Verify & close | sysadmin_devops (AI) | Reality gate: runs probes, advances to LIVE → CLOSED if all pass |
Quick-reference: the change lifecycle commands
# 1. Submit
POST /change-request {"problem": "...", "submitter": "eric-trapman-ceo", "impacted_columns": ["Technology"]}
# 2. Triage (AI)
POST /change-request/CHG-NNN/run-phase {"to_phase": "triage"}
# 3. Solution (AI)
POST /change-request/CHG-NNN/run-phase {"to_phase": "solution"}
# 4. Aggregate (AI)
POST /change-request/CHG-NNN/run-phase {"to_phase": "aggregate"}
# 5. Cost/Benefit (AI)
POST /change-request/CHG-NNN/run-phase {"to_phase": "cost_benefit"}
# 6. CAB — YOU READ AND DECIDE
GET /change-request/CHG-NNN ← read the plan
POST /change-request/CHG-NNN/advance {"to_phase": "cab_go", "actor": "eric-trapman-ceo", "note": "..."}
# 7. Tests (AI)
POST /change-request/CHG-NNN/run-tests {"tier": "PARTIAL"}
# 8. Execute — developer writes the code (AI)
POST /change-request/CHG-NNN/execute-plan {"actor": "developer"}
# 9. Verify & close (AI)
POST /change-request/CHG-NNN/verify-and-close {"tier": "PARTIAL", "actor": "sysadmin_devops"}
# 10. Deploy — YOU commit + push + trigger
git add <files> && git commit -m "feat: CHG-NNN — ..." && git push origin master
ssh root@engine.accommodus.com systemctl start dm-deploy.service
Technology Column
5.1 Infrastructure
Security posture (T2): no public Postgres port; API bound to 127.0.0.1 only; nginx terminates HTTPS; firewall: 22/80/443 only; all tokens are high-entropy (openssl rand -hex 24).
5.2 The Board — Postgres Message Store
The board is the single source of truth. It is append-only, never updated or deleted. A row existing IS the log entry.
-- messages table (board.py)
round_id TEXT -- groups messages into a round
from_role TEXT -- role that posted this message
phase TEXT -- e.g. "analysis", "snapshot_ref", "gate_state_ref"
body TEXT -- message content
meta JSONB -- structured metadata (snapshot_id, gate_state, etc.)
ts TIMESTAMPTZ -- auto-set on insert
-- Additional tables
decisions -- round_id → approved/rejected annotation
ledger -- one record per round (append-only audit)
change_requests -- change lifecycle rows (one row per state advance)
lane_routing -- lane classification history per change_ref
findings -- known-problem register (FND-NNN)
5.3 File Stores (engine/model/ and engine/logs/)
| File | Purpose | Sensitive? |
|---|---|---|
model/gate_state.json | Current gate state (armed, e_stop, execution_mode, auth fields) | No |
model/gate_authorizations.json | Authorization artifact registry (all auth records) | No |
model/tr_session.json | TR credentials: phone, PIN, WAF token, session cookies, account IDs | ⚠️ YES |
model/candidates.json | Trade candidate records (all fields for scoring) | No |
model/portfolio.json | Current portfolio positions | No |
model/math_bundle.json | Active governed math bundle version reference | No |
model/runhold_state.json | Current RUN/HOLD mode + active round tracking | No |
logs/gate_audit.jsonl | Append-only gate event stream | No |
logs/role_steps.jsonl | Per-role step log (G-D observability) | No |
logs/observability_events.jsonl | System events and alerts | No |
logs/runhold_audit.jsonl | RUN/HOLD mode change history | No |
5.4 Web Interfaces
| URL | Page | Auth required | Purpose |
|---|---|---|---|
/portal | portal.html | Token (client-side) | Main operational view: gate indicator, candidate scores, CRs, costs, portfolio |
/council.html | council.html | Token (client-side) | Run rounds, view past transcripts, submit questions |
/cockpit.html | cockpit.html | Token (client-side) | CEO cockpit: roles, models, change queue, one-click advance |
/status.html | status.html | None | System health tile view (roles count, DB, drift, gate, mode) |
/index.html | index.html | None | Original AIM grid (static reference view) |
/manual | manual.html | None | This document |
5.5 Engineering Lessons L1–L8 (Technology rulebook T6)
Hard-won rules from real self-builds. Each was a failure once. Never repeat them.
| Lesson | Rule | Evidence |
|---|---|---|
| L1 | Author in the tool, land on the host. The container cannot push to git or run docker compose. EXECUTE = author + validate + persist + HOLD; a host-side applier writes the files. | G-018 |
| L2 | Build to the acceptance contract / reality gate surface. A capability that exists as internal functions but not as the exact live HTTP surface the gate probes is a 404 and the change is HELD. | G-019 |
| L3 | Import convention: bare-name, no engine.*. PYTHONPATH=/app/engine. from engine.board import ... raises ModuleNotFoundError. | G-019 |
| L4 | Never wholesale-replace engine/api/main.py. It hosts 30+ routes. Add routes with action='append' or a minimal edit that keeps all existing lines. | G-019 |
| L5 | Read rules as data. Trading/business rules are READ from scoring_config.yaml at runtime, never written as literals in code. | G-007, P1 |
| L6 | After a host-side developer commit, push from the host. The container does NOT push. Future git pull deploys must be fast-forwards. | G-018 |
| L7 | Rebuild with GIT_COMMIT + EXPECTED_COMMIT exported. export GIT_COMMIT=$(git rev-parse --short HEAD) EXPECTED_COMMIT=$(git rev-parse --short HEAD) before docker compose up --build. | CHG-002 |
| L8 | External API calls must retry transient failures with exponential backoff + jitter, then fail closed. Never swallow a dead upstream. Do NOT retry 4xx. | G-020 |
Trade Republic Integration
6.1 Authentication Flow
6.2 First-Time Setup
- Open
https://app.traderepublic.comin Chrome and log in. - Press F12 → Application → Local Storage →
https://app.traderepublic.com. Copy the value ofawswaf_session_storage. - Run:
python engine/tr_client.py --phone +31634132095 --pin 7496 --waf-token <value> - Approve the push notification in the TR app within 3 minutes.
- Session saved to
engine/model/tr_session.json. Subsequent runs need no arguments.
Required TR v2 Headers
| Header | Value | Source |
|---|---|---|
x-tr-platform | web | Fixed |
x-tr-app-version | 15.7.0 | Fixed (match TR web app version) |
x-tr-device-info | base64-encoded JSON with stableDeviceId, model, browser, os, screen, timezone, etc. | Generated from device_id in session |
x-aws-waf-token | value of awswaf_session_storage from browser Local Storage | One-time from browser; persisted in tr_session.json |
6.3 Portfolio Data Structure
{
"positions": [
{
"isin": "US...",
"category": "stock",
"net_size": 100,
"average_buy_in": 14.20,
"current_price": 15.80,
"unrealized_profit": 160.0,
"unrealized_profit_pct": 11.27,
"invested_value": 1420.0,
"current_value": 1580.0
}
],
"position_count": 1,
"cash": {"amount": 500.0, "currency": "EUR"},
"savings_plans": [
{"id": "...", "isin": "AT0000652011", "amount": 50, "interval": "monthly", "start_day": 16}
],
"portfolio_status": "ACTIVE",
"has_invested": true,
"fetched_at": "2026-06-22T10:00:00Z"
}
tr_session JWT expires after ~5 minutes. There is no working refresh endpoint — the client auto-relogins using stored phone+PIN, which requires a new app approval. Plan for this in operational procedures.
Operational Procedures
7.1 Pre-Round Checklist
[ ] GET /status → healthy=true, roles_count=20, db_connected=true
[ ] GET /status → effective_armed=true, system_mode=RUN
[ ] GET /status → drift=false (version_commit matches expected_commit)
[ ] GET /candidates/score → candidate data fresh (catalyst + price ≤ 1 day old)
[ ] python engine/tr_client.py → current positions visible
[ ] Authorization expiry checked (expires_at > today + 14 days)
[ ] No active e_stop (gate.e_stop=false)
7.2 Running a Round
curl -X POST https://engine.accommodus.com/ask \
-H "X-Board-Token: <token>" \
-H "Content-Type: application/json" \
-d '{
"question": "ACMR has a Phase 3 readout in 45 days, Q=74, V=0.031. Current portfolio has no open positions. Should we enter, and at what size?",
"name": "ACMR-20260622"
}'
The name field is optional — used for cost attribution by name (GET /costs/by-name).
7.3 Acting on a Decision
- Read the full transcript in the portal or via
GET /decisions/latest. Pay attention to the Devil's Advocate and the Risk Review dissents. - Check the gate stamp on the decision: confirm
effective_armed=truewas captured at intake. - Approve or reject with a human-authored note:
curl -X POST https://engine.accommodus.com/decisions/{round_id}/approve \ -H "X-Board-Token: <token>" \ -d '{"actor": "eric-trapman-ceo", "note": "Entering ACMR. Council thesis aligns."}' - Execute the trade manually in the TR app (search by ISIN, not ticker).
- Update the portfolio record:
curl -X POST https://engine.accommodus.com/portfolio \ -H "X-Board-Token: <token>" \ -d '{"ticker":"ACMR","entry_date":"2026-06-22","entry_price_usd":14.20,"shares":100}'
7.4 E-Stop and Emergency Procedures
# Set e-stop (instant, independent of armed state)
curl -X POST https://engine.accommodus.com/gate/e-stop \
-H "X-Board-Token: <token>" \
-d '{"actor": "eric-trapman-ceo"}'
# Clear e-stop (requires TWO distinct named principals)
curl -X POST https://engine.accommodus.com/gate/e-stop/clear \
-H "X-Board-Token: <token>" \
-d '{"cleared_by": "eric-trapman-ceo", "confirmed_by": "second-person-name"}'
7.5 Deploying Changes
All deploys go through the locked dm-deploy.service invariant. Do not run docker compose by hand — it bypasses the truthful commit-stamp step and the reality gate.
Standard deploy (human-authored commit already pushed to origin)
# On local machine — commit and push your changes
git add <files>
git commit -m "feat(CHG-NNN): ..."
git push origin master
# On server — trigger the deploy service (SSH or via cockpit)
systemctl start dm-deploy.service
# Watch progress
journalctl -u dm-deploy.service -f
dm-deploy.sh exports GIT_COMMIT and EXPECTED_COMMIT automatically before rebuilding, and runs the reality gate for the open change. Do not export these yourself — let the service handle it.If dm-deploy.service is in a failed state
systemctl reset-failed dm-deploy.service
systemctl start dm-deploy.service
A systemd service that exits non-zero goes into failed state and cannot be started again until reset-failed clears it.
Verify the deploy
curl https://engine.accommodus.com/status | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if not d.get('drift') else 'DRIFT')"
7.6 The EXECUTE → Host Apply Mechanism
When the developer role runs EXECUTE, it does NOT directly write to disk or run docker compose. Instead it authors a structured patch, persists it in the board, and waits. A human-triggered host-side script then fetches, applies, and deploys it. This separation is a locked security invariant (L1 / G-018).
Why the separation exists
The engine runs inside an isolated Docker container with no host mount and no Docker socket. The developer AI can read repo files (delivered read-only as EXECUTE context) but cannot write to the host filesystem or rebuild itself. This prevents an AI role from unilaterally deploying arbitrary code — a human always owns the final trigger.
The two-phase flow
Step-by-step: how to re-run EXECUTE
-
Open changes.html — go to https://engine.accommodus.com/changes.html and enter your board token → Connect.
-
Find the CR — the list shows all open change requests. Click on the one you want (e.g. CHG-005 or CHG-006) to expand its timeline panel.
-
Check the current phase — the CR must be in Executing phase. If it is, you will see a NEXT STEPS section at the top of the panel with an Execute (AI — ~90s) button.
-
Click "Execute (AI — ~90s)" — the developer AI (claude-opus-4-8) reads the approved aggregate and the current repo files, then authors a structured patch. This takes ~90 seconds. A status message appears during the wait.
-
Read the result in the timeline — when done, a new
developer executerow appears:- PATCH READY — success. The patch is persisted in the board. Note the host command shown at the bottom of the entry (it will say
cd /root/dm-engine && ./engine/host/apply_authored_patch.sh CHG-NNN). - EXECUTE HELD — the developer blocked and did not produce a patch. Read the reason. Common causes: a file needed for the fix is truncated in context (file > 64 KB), or the fix requires a structural change the AI flags as unsafe. In this case, escalate to a direct fix (see below).
- PATCH READY — success. The patch is persisted in the board. Note the host command shown at the bottom of the entry (it will say
-
SSH to the server and run the host applier — replace
CHG-NNNwith the actual change reference (e.g.CHG-005):
This script (see §7.6.1 below) fetches the patch, writes the files, commits, rebuilds, verifies, and pushes to origin. Watch forcd /root/dm-engine ./engine/host/apply_authored_patch.sh CHG-NNN== done. Confirm /status version_commit ==at the end. -
Advance to Quality Gate — back in changes.html, click Advance to Quality Gate →. Then click Run Quality Gate and check the results. If all probes pass, the CR advances to Live → Closed automatically.
7.6.1 apply_authored_patch.sh — what it does
Location on the server: /root/dm-engine/engine/host/apply_authored_patch.sh
| Step | What happens | Notes |
|---|---|---|
| 1. Fetch patch | Calls GET /change-request/CHG-NNN/patch on the local engine to retrieve the persisted patch artifact from the board | Fails if no patch was persisted (Execute never ran or was held) |
| 2. Write files | Creates/replaces/appends the files declared in the patch under /root/dm-engine/ | Sandbox-checked: paths must be inside the engine tree |
| 3. Commit | git add -A && git commit attributed to dm-developer-role — the audit trail shows the AI authored this commit | Local commit only — no push yet |
| 4. Rebuild | docker compose -f engine/docker-compose.yml -p dm-engine up -d --build with truthful GIT_COMMIT/EXPECTED_COMMIT stamps | Rebuild fails → script exits non-zero; run reset-failed before retrying |
| 5. Push | git push origin HEAD:master — publishes the developer-authored commit to GitHub so origin is never behind the running code | Skipped with --no-push |
Flags
# Normal (apply + rebuild + push to origin)
cd /root/dm-engine && ./engine/host/apply_authored_patch.sh CHG-005
# Apply and commit, but skip the docker rebuild (manual deploy later)
cd /root/dm-engine && ./engine/host/apply_authored_patch.sh CHG-005 --no-deploy
# Apply, rebuild, but don't push to origin (leaves origin behind — avoid unless intentional)
cd /root/dm-engine && ./engine/host/apply_authored_patch.sh CHG-005 --no-push
/root/dm-engine (not a subdirectory) so that engine/docker-compose.yml resolves correctly. Always cd /root/dm-engine first.
systemctl reset-failed dm-deploy.service && systemctl start dm-deploy.service
What CHG-NNN to use
Pass the change reference of the CR that just ran Execute — the same reference shown in the EXECUTE HELD / PATCH READY board row. For example, if CHG-005 was just executed, run:
./engine/host/apply_authored_patch.sh CHG-005
The script reads the latest persisted patch for that reference. If you run it a second time after the patch was already applied, it will re-fetch and re-apply — which is idempotent for file writes but will attempt a new git commit and rebuild.
API Reference
8.1 Authentication
All endpoints except /health, /status, /model/data, web pages and /steps/recent require a board token.
# Header (preferred)
X-Board-Token: <token>
# Or query string
GET /candidates?token=<token>
The token is set in BOARD_TOKEN in the server's .env. When empty, the gate is open (local dev only).
8.2 Health & Status
Health & Status endpoints
| Method | Path | Auth | Returns |
|---|---|---|---|
| GET | /health | None | {status: "ok"} |
| GET | /status | None | Full health: roles_count, db_connected, drift, gate state, system_mode, round_active, config_drift, observability |
| GET | /status.html | None | HTML status tile page |
| GET | /auth/check | Token | {ok: true} if token valid |
8.3 Council — Rounds & Roles
Council endpoints
| Method | Path | Body / Params | Returns |
|---|---|---|---|
| POST | /ask | {question, roles?, change_ref?, round_id?, name?} | round_id, transcript, messages, cost, gate_state_stamp, snapshot_id |
| GET | /round/{round_id} | — | All messages for a round |
| GET | /inbox/{role} | — | Messages addressed to this role |
| GET | /roles | — | List of 20 role slugs |
| GET | /steps/recent | ?n=50&role= | Recent role step log entries |
8.4 Candidates & Scoring
Candidates endpoints
| Method | Path | Body / Params | Returns |
|---|---|---|---|
| GET | /candidates | — | All candidates sorted by ticker |
| POST | /candidates | {ticker, runway_months_past_catalyst, lead_asset_pct_of_value, market_cap_band, clean_exit_feasible, liquidity_exitable, ruin_risk, adjusted_pos_score, expected_magnitude_score, date_confidence, days_to_catalyst, p, R_up, R_down, notes, ...} | Saved candidate record |
| DEL | /candidates/{ticker} | — | 404 if not found |
| GET | /candidates/score | — | Ranked list with Q, V, status (scored/parked/rejected), gate failures, config_version |
8.5 Portfolio & Timeline
Portfolio endpoints
| Method | Path | Body / Params | Returns |
|---|---|---|---|
| GET | /portfolio | — | Current positions list |
| POST | /portfolio | {ticker, entry_date, entry_price_usd, shares, current_advice?, catalyst_date?, notes?} | Saved position |
| DEL | /portfolio/{ticker} | — | 404 if not found |
| GET | /timeline | — | Positions sorted by catalyst_date, with days_to_catalyst and exit_window flag (exit_window = catalyst_date ≥ today + 14d) |
| GET | /portfolio/snapshots | ?portfolio_id=&limit=50 | Snapshot index |
| GET | /portfolio/snapshots/{id} | — | Full snapshot record |
| GET | /portfolio/snapshots/{id}/verify | — | Hash verification result |
| POST | /portfolio/snapshot | — | Capture immutable snapshot of current portfolio |
| POST | /portfolio/snapshot/manual | {portfolio_id, payload} | Snapshot from supplied payload |
8.6 Decisions & Ledger
Decisions & Ledger endpoints
| Method | Path | Body / Params | Returns |
|---|---|---|---|
| GET | /decisions | — | All rounds with decisions, newest first |
| GET | /decisions/stats | — | consecutive_approved, total_approved, target:30, target_met |
| GET | /decisions/latest | — | Latest decision + gate summary (C-G9-01) |
| GET | /decisions/{round_id}/detail | — | Transcript + decision state + gate summary |
| POST | /decisions/{round_id}/approve | {note, actor} | Decision annotation + auto-ledger write |
| POST | /decisions/{round_id}/reject | {note, actor} | Decision annotation + auto-ledger write |
| GET | /ledger | ?limit=100 | All ledger records (append-only), newest first + summary |
| GET | /ledger/{round_id} | — | Ledger record + hash_verified |
| POST | /ledger | {round_id, round_date, input_ref, output_ref, status, recorded_by, snapshot_id?, math_bundle_version_id?} | Manual ledger record (422 if round_id exists) |
8.7 Change Management
Change management endpoints
| Method | Path | Body / Notes |
|---|---|---|
| POST | /change-request | {problem, submitter, impacted_columns, triage?} |
| GET | /change-requests | All CRs |
| GET | /change-request/{ref} | CR detail + history |
| POST | /change-request/{ref}/advance | {to_phase, actor, note} — manual phase advance |
| POST | /change-request/{ref}/run-phase | {to_phase, actor} — AI produces phase content |
| POST | /change-request/{ref}/run-tests | {tier: PARTIAL|FULL} — acceptance test run (read-only) |
| POST | /change-request/{ref}/execute-plan | {plan, actor, patch?} — server-side execution (allowlisted) |
| POST | /change-request/{ref}/verify-and-close | {tier, actor} — reality gate + LIVE + CLOSED |
| POST | /change-request/{ref}/retire | {basis, note, actor, linked_change_ref?} — terminal retire (abandoned/duplicate/superseded/absorbed-by) |
| GET | /change-request/{ref}/lane | Lane classification + history |
| POST | /change-request/{ref}/lane/propose | {proposed_lane, actor, rationale} |
| POST | /change-request/{ref}/lane/classify | {confirmed_lane, cm_actor, rationale, proposal_matched} |
| POST | /change-request/{ref}/lane/reclassify | {new_lane, cm_actor, reason, lifecycle_stage} |
| GET | /change-request/{ref}/bundle | Bundle detail (findings + workarounds-to-retire) |
| GET | /change-request/{ref}/patch | Latest validated patch artifact (404 if none) |
| POST | /release-bundle | Assemble release bundle from open findings |
| POST | /finding | {symptom, source, submitter, impacted_columns?, spec_reference?, workaround?} |
| GET | /findings | All findings (FND-NNN) |
| GET | /finding/{ref} | Finding detail |
| POST | /finding/{ref}/status | {status, actor, note} |
8.8 Gate System
Gate system endpoints
| Method | Path | Body / Notes |
|---|---|---|
| GET | /gate/history | Append-only gate audit event log, oldest first |
| GET | /gate/authorization/{id} | Authorization artifact by ID |
| POST | /gate/authorize | {authorization_id, authorized_by, scope, expires_at} → RECORDED state |
| POST | /gate/approve/{id} | {cab_record_ref} → APPROVED state |
| POST | /gate/arm | {authorization_id, actor} → validates 9 preconditions; ARMED or DENIED + audit |
| POST | /gate/disarm | {actor} → resets armed fields; preserves execution_mode |
| POST | /gate/revoke/{id} | {revoked_by, reason} → irreversible; auto-disarms gate |
| POST | /gate/e-stop | {actor} → instant block; effective_armed drops to false |
| POST | /gate/e-stop/clear | {cleared_by, confirmed_by} → requires two distinct named principals |
| POST | /execution-mode | {mode: dry_run|real_money, actor} → blocked while gate is armed |
8.9 RUN/HOLD Mode
RUN/HOLD endpoints
| Method | Path | Body / Notes |
|---|---|---|
| GET | /system/mode | Current mode (RUN/HOLD), round_active, active_round_id, last change record |
| POST | /system/hold | {actor, reason} → new submissions rejected |
| POST | /system/run | {actor, reason} → submissions accepted again |
| GET | /system/audit | ?n=50 — last N RUN/HOLD events, newest first |
8.10 Costs & Telemetry
Cost endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /costs/by-role | Cumulative spend since last reset, by role |
| GET | /costs/by-cr | Spend per change_ref (CHG-* rounds only) |
| GET | /costs/by-round | Spend per round_id (all rounds) |
| GET | /costs/by-name | Spend per /ask name label; unlabelled = "untagged" |
| POST | /costs/reset | {actor, confirm: true} — moves display baseline; underlying rows preserved |
8.11 Governance & Config
Governance endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /governance/config/fingerprint | Current live config state fingerprint (SHA-256) |
| GET | /governance/config/drift | Compare fingerprint to approved baseline; surfaces alert if drifted |
| POST | /governance/config/approve-fingerprint | {fingerprint, approved_by} — set new approved baseline |
| GET | /governance/controls | Pre-authorized operational controls catalog |
| GET | /governance/emergency-actions | ?limit=50 — emergency action log (append-only) |
| POST | /governance/emergency-action | {actor, reason, target_class, action_performed, before_value?, after_value?, incident_ref?} |
| GET | /models/governance | Role → owner → enforcement_point → capability_class table |
| GET | /models/preflight | Per-role availability against live catalog (fail-closed 503) |
| GET | /models/fit-inputs | Best-fit review inputs (separate from availability) |
| GET | /models/gate/compliance | Compliance role model validation gate |
| GET | /models/gate/trade-expert | Trade expert role model validation gate |
8.12 Observability
Observability endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /observability/events | ?n=50 — last N structured events, newest first |
| GET | /observability/alerts | Current active alerts list |
| POST | /observability/alerts/raise | {alert_id, alert_type, severity, component, message, entity_id?} |
| POST | /observability/alerts/{id}/clear | {message?} |
8.13 Math Bundle & Artifacts
Math bundle & artifact endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /math/bundle | Active governed rule bundle version reference |
| GET | /math/bundle/ref | Bundle ref block for embedding in cycle outputs |
| GET | /math/bundle/history | All bundle activations (append-only), oldest first |
| POST | /math/bundle | {bundle_id, bundle_content, actor} — register new bundle; computes SHA-256 |
| GET | /artifacts | List whitelisted artifacts (RULEBOOK.md, business.md, etc.) |
| GET | /artifacts/{name} | Serve artifact file as text/markdown |
| GET | /functionality | Authoritative Functionality Baseline (AFB) — as-designed reference |
8.14 System Model & Question Front Door
System model endpoints
| Method | Path | Notes |
|---|---|---|
| GET | /model/data | Live system-model data blob (runtime projection — public, read-only) |
| GET | /sources/analysis | Information origin: top domains, freshness, grounding summary |
| GET | /sources/change/{ref} | Sources used for a specific change |
| GET | /sources/role/{role} | Sources attributed to a role |
| POST | /handover/validate | {handover} — validate against Decision Surface contract v1.0.0 |
| GET | /handover/contract | Frozen Decision Surface contract spec |
| POST | /question | {text, intent?, change_fields?} — answer-only or draft change intent |
Role Reference
Detailed reference for each of the 20 AI roles — what they produce, which model they use, and what questions they answer.
Sonar Roles (5) — Perplexity Sonar API, web-grounded
analyst_desk
Model: deep-research (Sonar) · Pinned: No · Mode: manual
Writes the candidate brief: trial design, mechanism, prior readouts, ClinicalTrials data, dilution risk, CEO commentary, price action. The primary research synthesist — produces the document all other roles read. Does not estimate p or R values (those belong to risk_review).
risk_review
Model: sonar-reasoning-pro (Sonar) · Pinned: No · Mode: manual
Estimates the probability input p (likelihood the path re-rates before exit), maps downside scenarios, sizes R_down, and flags tail risks (FDA precedent, trial design weaknesses, regulatory timeline slippage). Answers: "What is the probability this trade works before our exit?"
portfolio_manager
Model: pro-search (Sonar) · Pinned: No · Mode: manual
Assesses fit with current portfolio exposure, correlation to open positions, sizing constraints, and total risk budget. Answers: "How much should we put in and why, given what we already hold?"
perplexity_expert
Model: pro-search (Sonar) · Pinned: No · Mode: auto
Runs real-time searches: recent news, SEC filings, catalyst calendar, conference presentations, analyst coverage, IBB trend, sentiment signals. The "newspaper" role — it tells you what the world knows right now. Also serves as a data-freshness check for the minimum daily set.
app_support
Model: fast-search (Sonar) · Pinned: No · Mode: auto
Handles data lookup, fact verification, the change-process front door (triages inbound questions as operational vs change-requiring), and the known-problem register. The first point of contact for anything "is this a bug or working as designed?"
Agent Roles (15) — Perplexity Agent API
coordinator
Model: openai/gpt-5.2 · Pinned: Yes
Orchestrates the round: routes the question to the right roles, sets the framing for each role call, and assembles all responses into the final transcript and decision surface. The coordinator's output is what you read in the portal. It does not add its own opinion — it synthesises.
change_manager
Model: openai/gpt-5.4 · Pinned: Yes
Owns the Information column's change lifecycle. Runs triage, solution, aggregate, and cost-benefit phases. Classifies lane routing. Ensures roles are asked in isolation. Governs what is a Calculation change vs Instruction change. Does NOT give the worth-it verdict — that is the human CAB.
overall_process_owner
Model: openai/gpt-5.4 · Pinned: Yes
Monitors cross-process integrity. Required presence in G9 CAB quorum. Challenges whether a change touches processes outside its stated scope. The "did we miss any affected part of the system?" role.
business_process_owner
Model: anthropic/claude-sonnet-4-6 · Pinned: Yes
Guards Business column rules during change review. Verifies that no proposed change violates B1-B6. Flags if a proposed instruction change is actually embedding a calculation.
cio
Model: openai/gpt-5.4 · Pinned: Yes
Challenges the investment thesis from an information-integrity perspective. Required in G9 CAB quorum. Asks: "Do we actually have the information we claim to have? Is our edge thesis still intact? Has the market caught up?"
cto
Model: openai/gpt-5.5 + web_search · Pinned: Yes
Reviews technology and data quality. The only Agent-type role with web_search enabled. Checks: is the data source reliable? Are there API changes that would break our feeds? Is the scoring engine reading the correct config version?
devils_advocate
Model: anthropic/claude-opus-4-8 · Pinned: Yes
Produces a structured, steel-manned counter-argument to every thesis. Deliberately ignores the analyst brief's framing and argues the case against. The most important role to read carefully — its dissents are where the council catches its own blind spots. Uses Opus for maximum reasoning depth.
product_owner
Model: anthropic/claude-sonnet-4-6 · Pinned: Yes
Guards output quality and scope. Checks that the round actually answered the question asked, that the decision surface is complete and reviewable, and that no role overstepped its scope (e.g. a Sonar role embedding math).
it_architect
Model: openai/gpt-5.4 · Pinned: Yes
Reviews infrastructure and integration concerns. For investment rounds: are there any data or integration issues that could compromise the candidate brief? For change rounds: does the proposed change respect the technology architecture principles (T1-T2)?
solution_architect_it_pm
Model: openai/gpt-5.4 · Pinned: Yes
Reviews end-to-end solution coherence. Checks that all moving parts of a change (code, config, database, API, UI) are accounted for and that the solution is internally consistent. The "does this actually hang together?" role.
quality_manager_auditor
Model: openai/gpt-5.2 · Pinned: Yes
Runs the quality check on the round output: is the transcript complete, are citations present, are all required sections populated? Required in G9 CAB quorum. Also writes the audit trail assertion for ledger records.
developer
Model: anthropic/claude-opus-4-8 · Pinned: Yes
Authors and validates code patches for change requests. Produces the structured patch artifact (files, actions, contents) that the host applier applies. Never runs git push or docker compose (separation boundary L1). Uses Opus for maximum code quality.
sysadmin_devops
Model: openai/gpt-5.4 · Pinned: Yes
Handles operational and deployment concerns. Verifies that deployment steps are correct, that health checks will pass, and that rollback paths exist. The role that runs the execute-plan phase and drives the reality gate.
trade_expert
Model: openai/gpt-5.4-nano · Pinned: Yes · Governance: preapproved fallback model
Reviews biotech trading mechanics and exit feasibility. Checks: is the stated exit window actually achievable given average daily volume? Are there lock-up periods or insider selling windows? Does the pre-binary exit thesis hold for this specific catalyst type? Uses nano (fast, lower cost) as the model gate has a preapproved fallback.
compliance
Model: openai/gpt-5.4-nano · Pinned: Yes · Governance: strict fail-closed
Regulatory and governance checks. For investment rounds: any regulatory flags on the candidate (FDA clinical holds, embargo periods, material non-public information risks)? For change rounds: does the proposed change comply with the governance framework? The compliance model gate is strict fail-closed — a missing or invalid model blocks finalization.
Enrichment Process & Activity Inspector
How candidate data gets gathered, how to track what happened in every round, and how to diagnose why fields are still UNKNOWN.
10.1 The Enrichment Pipeline
A candidate starts with all scoring fields set to UNKNOWN and enrichment_count = 0. Before it can be ranked or recommended, it must be enriched — meaning real data must be gathered and written into those fields. The machine uses a 7-role enrichment round to do this.
Why enrichment is separate from scoring
Scoring is deterministic — given known field values, Q and V are always the same. Enrichment is data gathering — roles search the web, parse filings, and estimate probabilities. Separating them means the score engine is a pure function with no side effects, while enrichment can be re-run to refresh stale data.
The 7 enrichment roles
| Role | What it gathers | Has web search |
|---|---|---|
coordinator | Orchestrates the round; assigns research sub-tasks | No |
analyst_desk | Catalyst timeline, clinical data, trial design, prior readouts | Yes (Perplexity deep-research) |
risk_review | FDA precedent, competitive landscape, binary risk assessment | Yes (Perplexity sonar-reasoning-pro) |
devils_advocate | Bearish arguments, dilution risk, sector headwinds | No (Claude Opus) |
portfolio_manager | Price/volume trend, market cap, IBB sector context | Yes (Perplexity pro-search) |
trade_expert | Exit feasibility, ADV, exit window mechanics | No |
compliance | Regulatory flags, material non-public risk | No |
Isolation principle in enrichment
Each role receives only the enrichment question — they do not see each other's answers. The change_manager role is the final aggregator: it receives ALL role outputs and synthesizes them. The change_manager is the only role that sees the full picture and is therefore responsible for writing the final --- ENRICHMENT JSON --- block.
The coordinator runs in isolation with no web search. It cannot gather real data. Only
change_manager aggregates all role answers and therefore has the full picture needed to fill 18 scoring fields. The enrichment question ends with: "IMPORTANT FOR THE FINAL SYNTHESIS: after aggregating all role inputs, the synthesis MUST end with a single JSON object on the line immediately after the EXACT literal separator: --- ENRICHMENT JSON ---"
What happens after the round
- 7 roles run in parallel — each receives the enrichment question independently via
POST /ask. - change_manager aggregates — it receives all 7 role answers and writes a synthesis ending with
--- ENRICHMENT JSON ---followed by a JSON object. - Browser parses the JSON block —
candidates.htmlsearches the synthesis text for the separator, extracts the JSON object, and immediatelyPOST /candidatesto write the fields into the candidate store. - Score engine re-runs — the leaderboard refreshes. If enough fields are now known, Q and V are computed and the candidate moves from
parkedtoscored.
The 18 scoring fields
| Field | Type | Used in | Missing → effect |
|---|---|---|---|
days_to_catalyst | number | V (T_exit), Q (timing) | V = UNEVALUABLE → parked |
date_confidence | high/medium/low | Q timing multiplier | No multiplier applied |
catalyst_date | ISO date | Display only | No display date shown |
adjusted_pos_score | 0–100 | Q (PoS bucket, 25%) | Q bucket → UNEVALUABLE |
expected_magnitude_score | 0–100 | Q (magnitude bucket, 25%) | Q bucket → UNEVALUABLE |
edge_intactness_base | 0–100 | Q (edge bucket, 15%) | Falls back to x_signal if set |
x_signal | 0–100 | Q (edge bucket fallback) | Edge bucket → UNEVALUABLE |
financial_runway_score | 0–100 | Q (runway bucket, 10%) | Q bucket → UNEVALUABLE |
runway_months_past_catalyst | months | Hard gate ≥18 | Gate = UNEVALUABLE → parked |
lead_asset_pct_of_value | % | Hard gate >50 | Gate = UNEVALUABLE → parked |
market_cap_band | micro/small/mid | Hard gate, R_down fallback | Gate = UNEVALUABLE → parked |
clean_exit_feasible | bool | Hard gate | Gate = UNEVALUABLE → parked |
liquidity_exitable | bool | Hard gate | Gate = UNEVALUABLE → parked |
ruin_risk | bool | Immediate reject if true | Defaults to false |
x_crowded | bool | Q crowding penalty | Defaults to false (no penalty) |
p | 0–1 | V (probability of upside) | V = UNEVALUABLE → parked |
R_up | return % | V (upside return) | V = UNEVALUABLE → parked |
R_down | return % | V (downside, fallback by band) | Uses cap-band default if UNKNOWN |
UNEVALUABLE vs scored vs parked
A candidate is parked when any hard gate field is UNKNOWN — the engine cannot determine if it passes. It is rejected when a hard gate check definitively fails (e.g. ruin_risk=true). It is scored when all 5 hard gates pass and both Q and V can be computed. The enrichment_count counter increments each time enrichment fields are written; candidates with enrichment_count = 0 have never been enriched.
10.2 Running Enrichment — Step by Step
- Open the Leaderboard — navigate to https://engine.accommodus.com/candidates.html. Set your board token at the top.
-
Find a parked or unenriched candidate — candidates with red status dots or status
parkedneed enrichment. A candidate withenrichment_count = 0has never been enriched. -
Click "▶ Enrich" on the candidate row — this fires an enrichment round for that specific ticker. The button label changes to
Running… (~2–3 min). - Watch the status message — the page shows "Council deliberating… (~2–3 min)". The 7 roles run, then change_manager aggregates. You will see "✓ Enrichment complete · fields written" when done.
-
Leaderboard refreshes — the candidate's row updates. If the enrichment round produced all required fields, status changes from
parkedtoscoredand Q/V scores appear. - Inspect the round in Activity — click Activity ↗ in the navigation bar. Find the most recent round (top of list). Click it to see role dots and field status.
Open activity.html, select the round, and look for red dots. A red dot on
analyst_desk or risk_review usually means the Perplexity web search didn't return useful data for that ticker. Click the role chip to read the exact response. If the response says "No FDA calendar entries found" or "Insufficient public data", the candidate may be too obscure for automated enrichment — update fields manually using the Edit form in candidates.html.
10.3 Activity Inspector — How to Use It
The Activity Inspector (https://engine.accommodus.com/activity.html) is a three-panel diagnostic tool. It shows every round ever run, which roles responded, whether their responses were substantive, and the exact content of each API call.
Round list
(sorted newest first)"] --> B["Middle panel
Round detail
(roles + enrichment fields)"] B --> C["Right panel
Message content
(exact API response)"] style A fill:#161b22,stroke:#30363d,color:#c9d1d9 style B fill:#161b22,stroke:#30363d,color:#c9d1d9 style C fill:#161b22,stroke:#30363d,color:#c9d1d9
Left panel: Round list
- Each round shows its ID (truncated), timestamp, question preview, message count, and role count.
- A red dot next to a round means at least one role produced an error or empty response in that round.
- Use the search bar to filter rounds by ID or question text.
- The most recent round is always at the top.
Middle panel: Round detail
Clicking a round opens the detail panel with three sections:
-
Question block — the exact instruction sent to all roles. For enrichment rounds, this is the full enrichment prompt including all 5 data categories and the
--- ENRICHMENT JSON ---instruction. -
Enrichment field status — only visible for enrichment rounds. Shows all 18 scoring fields as colored pills:
- Green ✓ — field was filled with a real value (not UNKNOWN)
- Red ✗ — field is still UNKNOWN after this round
- Grey — enrichment JSON was not detected (not an enrichment round)
days_to_catalyst = 47). -
Role participation chips — one chip per role that participated. Color indicates response quality:
- Green dot — good response (≥200 chars, no error keywords)
- Orange dot — short response (50–200 chars) — may be incomplete
- Red dot — error or near-empty response (<50 chars, or contains ERROR:/BLOCK:/Exception)
- Grey dot — role did not participate in this round
Right panel: Message content
When you click a role chip, the right panel shows:
- Role status summary (good / short / error)
- Exact timestamp and model used
- Cost in USD for that call
- The full raw response text — exactly what the API returned
- For enrichment rounds, the
--- ENRICHMENT JSON ---separator is highlighted in orange so you can quickly locate where the structured output begins
1. Open Activity Inspector · find the enrichment round for that ticker
2. Look at the Enrichment field status — identify the red ✗ field
3. In the Role chips, find which roles have red dots
4. Click the
analyst_desk chip (most likely source for clinical/timeline fields) to read its exact response5. If the response says "I could not find..." or is very short, the web search didn't return data
6. Manually update the field via the Edit form in candidates.html
10.4 New API Endpoints (added with enrichment)
| Method | Path | Returns |
|---|---|---|
| GET | /rounds | All round_ids with metadata: ts, question_preview, roles_seen, message_count, has_error_roles |
| GET | /round/{round_id} | All messages for a specific round (existing endpoint) |
| GET | /roles/{role_slug} | Role definition markdown, model config, and full system prompt for a specific role |
GET /rounds — example response
{
"rounds": [
{
"round_id": "enrich-IBRX-2026-06-30T14:22:01",
"ts": "2026-06-30T14:22:01.123Z",
"question_preview": "Enrich trade candidate IBRX: gather all five information categories...",
"roles_seen": ["analyst_desk","change_manager","compliance","coordinator","devils_advocate","portfolio_manager","risk_review","trade_expert"],
"message_count": 10,
"has_error_roles": []
}
],
"count": 1
}
10.5 Common Enrichment Problems
| Symptom | Likely cause | Fix |
|---|---|---|
| All fields still UNKNOWN after enrichment | change_manager didn't write the JSON block; separator may be missing from its response | Open Activity, read change_manager response. If it's missing the separator, re-run enrichment. If it keeps failing, check that the enrichment question in candidates.html includes the exact separator instruction. |
| enrichment_count increments but fields don't update | Browser couldn't parse the JSON block (malformed JSON) | Open Activity, click change_manager, find the --- ENRICHMENT JSON --- line. Check that what follows is valid JSON. Common issue: change_manager adds prose after the closing brace. |
| analyst_desk has red dot | Perplexity rate limit or network error | Wait 30 seconds and re-run enrichment. analyst_desk uses deep-research mode which has lower rate limits. |
| days_to_catalyst = UNKNOWN despite trial dates being publicly known | Analyst found the date but expressed it as a range rather than a specific number | Read the analyst_desk response to find the date. Update manually: Edit the candidate in candidates.html, set days_to_catalyst and catalyst_date directly. |
| Candidate stuck at parked after enrichment | One or more hard gate fields still UNKNOWN or failing | Run GET /candidates/score to see the exact gate failure reason. Update the failing field manually if web sources don't have it. |