Decision Machine — System Manual

● ARMED real_money G9-AUTH-2026-001 · expires 2026-07-21 ⌂ Home
Quick Start

5-Step TL;DR for CEO

The minimum you need to get a portfolio recommendation in under 10 minutes.

  1. Check the gate — open portal and confirm ARMED · real_money badge is green. Or: GET /statuseffective_armed: true.
  2. Fetch your TR portfolio — run python engine/tr_client.py on your local machine, approve the push notification in the TR app. See your current positions and cash.
  3. Score candidatesGET /candidates/score returns every candidate ranked by V (velocity). Candidates failing a hard gate show eligible: false.
  4. Run a roundPOST /ask with your question and the top-scoring tickers. The 20-role council deliberates in 2–5 minutes. A transcript and recommendation appear in the portal.
  5. Decide and act — read the transcript (especially the Devil's Advocate dissent). POST /decisions/{round_id}/approve or /reject with a note. Execute manually in the TR app. Never hold through the binary.
Authorization expires 2026-07-21T23:59:59Z Renew before expiry: CEO initiates POST /gate/authorize → CAB approval → POST /gate/arm.
Part 1

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.

Figure 1.1 — System architecture (3-column AIM grid)
graph LR subgraph BIZ["BUSINESS — WHAT"] B1["B1: Edge thesis\n(locked)"] B2["B2: Score formula\nQ + V"] B3["B3: Enrichment\ntiering"] B4["B4: Info categories\n(5)"] B5["B5: Hard rule\non math"] end subgraph INFO["INFORMATION — HOW-GOVERNS"] I0["I0: Isolation\nPrinciple"] I1["I1: Configure\n& Approve"] I2["I2: Change\nlifecycle (4 lanes)"] I3["I3: G9 quorum\nrule"] end subgraph TECH["TECHNOLOGY — HOW-RUNS"] T0["T0: Paper-and-phones\nprinciple"] T1["T1: Architecture\nprinciples"] T2["T2: Security\nposture"] T5["T5: Store &\nhandover"] T6["T6: Eng. lessons\nL1–L8"] end BIZ -->|rules-as-data| TECH INFO -->|change lifecycle| TECH INFO -->|governance| BIZ TECH -->|scores + transcripts| CEO["👤 CEO\n(human)"] CEO -->|questions + decisions| TECH
📋

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 (messages table), 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.
Part 2

The AI Council

2.1 All 20 Roles

#RoleTypeModelPinnedModeFunction
1analyst_deskSonardeep-researchfreemanualDeep-research synthesis on candidate
2risk_reviewSonarsonar-reasoning-profreemanualProbability & downside risk assessment
3portfolio_managerSonarpro-searchfreemanualPosition sizing & portfolio fit
4perplexity_expertSonarpro-searchfreeautoReal-time web research on news / catalyst
5app_supportSonarfast-searchfreeautoData lookup & factual verification
6coordinatorAgentopenai/gpt-5.2pinnedmanualRuns rounds, assembles transcript & decision surface
7change_managerAgentopenai/gpt-5.4pinnedmanualGoverns change lifecycle (Information column)
8overall_process_ownerAgentopenai/gpt-5.4pinnedmanualCross-process integrity & G9 CAB quorum
9business_process_ownerAgentanthropic/claude-sonnet-4-6pinnedmanualBusiness rule adherence
10cioAgentopenai/gpt-5.4pinnedmanualInvestment thesis challenge (Information column)
11ctoAgentopenai/gpt-5.5 + web_searchpinnedmanualTechnology & data quality review
12devils_advocateAgentanthropic/claude-opus-4-8pinnedmanualStructured counter-argument on every thesis
13product_ownerAgentanthropic/claude-sonnet-4-6pinnedmanualOutput quality & scope control
14it_architectAgentopenai/gpt-5.4pinnedmanualInfrastructure & integration concerns
15solution_architect_it_pmAgentopenai/gpt-5.4pinnedmanualEnd-to-end solution coherence
16quality_manager_auditorAgentopenai/gpt-5.2pinnedmanualRound quality check & audit trail
17developerAgentanthropic/claude-opus-4-8pinnedmanualTooling & code concerns
18sysadmin_devopsAgentopenai/gpt-5.4pinnedmanualOperational & deployment concerns
19trade_expertAgentopenai/gpt-5.4-nanopinnedmanualBiotech trading mechanics & exit feasibility
20complianceAgentopenai/gpt-5.4-nanopinnedmanualRegulatory & 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.

Why isolation matters If the analyst_desk brief were visible to the devil's advocate before that role formed its challenge, the devil's advocate would anchor on the analyst's framing. The counter-argument would be weaker. Isolation forces genuinely independent thinking.

2.5 Round Execution — Step by Step

Figure 2.1 — Round execution sequence
sequenceDiagram participant CEO participant API as FastAPI /ask participant RH as RunHold participant PS as PortfolioSnapshot participant Gate participant RR as RoundRunner participant PX as Perplexity APIs participant Board as Board (Postgres) CEO->>API: POST /ask {question, tickers} API->>RH: check_intake() — reject if HOLD API->>PS: capture_current_portfolio() API->>Gate: get_state() — stamp execution_mode + effective_armed API->>RR: run_solution_round() loop Each role in DEFAULT_ROLES RR->>PX: call role (Sonar or Agent API) PX-->>RR: role response + citations end RR-->>API: transcript + messages + cost API->>Board: post snapshot_ref message API->>Board: post gate_state_ref message API-->>CEO: {round_id, transcript, gate_state_stamp} CEO->>API: POST /decisions/{id}/approve {note} API->>Board: record decision annotation API->>Board: write ledger record (append-only)
Part 3

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.

Figure 3.1 — Q score bucket weights
pie title Q Score Weights (must sum to 100) "Adjusted PoS (probability of re-rating)" : 25 "Expected Magnitude (upside size)" : 25 "Catalyst Timing (closeness × confidence)" : 25 "Edge Intactness (X-signal sub-score)" : 15 "Financial Runway (cash past catalyst)" : 10
BucketWeightInput field(s)Notes
Adjusted PoS25%adjusted_pos_score0–100 probability the catalyst path re-rates upward
Expected Magnitude25%expected_magnitude_score0–100 size of the upside move if re-rating occurs
Catalyst Timing25%days_to_catalyst, date_confidenceconfidence multiplier: high=1.0 / medium=0.7 / low=0.4
Edge Intactness15%edge_intactness_base, x_signal, x_crowdedX-signal sub-scores: mention_velocity_z (35%), quality_weighted_sentiment (30%), smart_money_ratio (20%), early_vs_retail (15%)
Financial Runway10%financial_runway_score0–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
VariableMeaningSource
pProbability the path re-rates before our exitrole hands over; stored in candidate record
R_upExpected upside return if re-rating occurs (fraction)role hands over
R_downExpected downside if thesis breaks (fraction)role hands over; default by market-cap band if UNKNOWN
T_exitDays to our exit (days_to_catalyst − 14 to 28 days)computed from days_to_catalyst
q_weightQ-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.

GateFieldRuleRationale
1. Cash runwayrunway_months_past_catalyst≥ 18 monthsCompany must survive past the catalyst
2. Lead asset concentrationlead_asset_pct_of_value≤ 50%Avoid single-asset binary-outcome names
3. Market cap bandmarket_cap_bandmicro / small / mid onlyEdge is in under-analysed caps; large = efficient
4. Clean exitclean_exit_feasible= trueMust be able to exit 2–4 weeks before binary
5. Liquidityliquidity_exitable= truePosition size must be exitable vs daily volume
+ Ruin riskruin_risk= falseAutomatic 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_exit already 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:

CategoryKey fieldsMin daily?
1. Catalyst / Timelinedays_to_catalyst, date_confidence★ Yes
2. Clinical / Scientifictrial design, prior data, mechanismNo (weekly)
3. Financial / Dilutioncash_runway, shelf_flag, share-count growth, CEO dilution language★ cash_runway + shelf_flag
4. Market / Priceprice, trend, volume★ Yes
5. Environment / SectorIBB 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.

Part 4

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.

Hard rule: Roles hold NO calculations. Any math hidden in a role prompt is forced to the calculation path (Lane 4, full CAB). This is enforced by the Change Manager.

4.2 Lane Routing — 4 Lanes

Figure 4.1 — Lane routing decision tree
flowchart TD A[New Change Request] --> B{Life-safety\nor regulatory\nemergency?} B -->|Yes| L1[Lane 1\nEmergency\nBreak-glass + retrospective CAB] B -->|No| C{Pre-authorized\nscope?} C -->|Yes| L2[Lane 2\nPre-authorized\nNo CAB needed] C -->|No| D{App Support\noperational?} D -->|Yes| L3[Lane 3\nApp Support\nLighter governance] D -->|No| L4[Lane 4\nStandard Governed Change\nFull lifecycle — DEFAULT]
LaneNameWhenCABObservation window
1EmergencyLife-safety / regulatory crisisRetrospective (post-action)No
2Pre-authorizedScope pre-approved by governanceNot requiredNo
3App SupportOperational / support-attestedScopedOptional
4StandardEverything else — defaultFull 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)

Figure 4.2 — Change lifecycle state machine
stateDiagram-v2 [*] --> INTAKE : POST /change-request INTAKE --> TRIAGE : run-phase triage TRIAGE --> SOLUTION : run-phase solution SOLUTION --> AGGREGATE : run-phase aggregate AGGREGATE --> COST_BENEFIT : run-phase cost_benefit COST_BENEFIT --> CAB : human advance CAB --> QUALITY : CAB GO vote QUALITY --> LIVE : execute-plan + verify-and-close LIVE --> CLOSED : reality gate passes CAB --> REJECTED : CAB OBJECT vote QUALITY --> REJECTED : NO-GO vote REJECTED --> SOLUTION : Re-route to Solution (AI — real re-run, CHG-043) QUALITY --> HOLD : reality gate fails INTAKE --> RETIRED : retire (abandoned/duplicate/superseded) TRIAGE --> RETIRED SOLUTION --> RETIRED AGGREGATE --> RETIRED COST_BENEFIT --> RETIRED
StateWho actsAPI callNotes
INTAKESubmitterPOST /change-requestRecords problem, submitter, impacted columns, triage hint
TRIAGEApp Support (AI)POST /change-request/{ref}/run-phase {to_phase: triage}AI classifies severity, urgency, lane
SOLUTIONImpacted roles (AI, isolated)run-phase solutionEach role gives its solution in isolation
AGGREGATEChange Manager (AI)run-phase aggregateSynthesizes complementary pieces; carries shortlist where options genuinely rival
COST_BENEFITChange Manager (AI)run-phase cost_benefitCredits consumed vs credits saved; cost vs benefit; human Perplexity credits input
CABHuman (CEO chairs)advance to_phase: cab_go or cab_objectWorth-it verdict is the human's to give, not the Change Manager's
QUALITYSysAdmin (AI) + reality gaterun-tests PARTIAL; execute-plan; verify-and-closeReality gate must pass before LIVE
LIVESystemauto on verify-and-close passBundle workarounds retired; findings resolved
CLOSEDSystemauto after LIVETerminal 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)

Figure 4.3 — G9 real-money gate authorization flow
flowchart LR A["CEO initiates\nAuthorization Record\n(Artifact K)"] --> B["POST /gate/authorize\n{authorization_id, authorized_by,\nscope, expires_at}"] B --> C["RECORDED state"] C --> D["CAB GO vote\n(Artifact M — quorum record)"] D --> E["POST /gate/approve/{id}\n{cab_record_ref}"] E --> F["APPROVED state"] F --> G["CEO attestation\n(Artifact L — PF-20 PASS)"] G --> H["POST /gate/arm\n{authorization_id, actor}"] H --> I{"9 preconditions\nchecked"} I -->|all pass| J["ARMED\neffective_armed = true"] I -->|any fail| K["DENIED\naudit event logged"] J --> L["Active: G9-AUTH-2026-001\nExpires: 2026-07-21"]

G9 CAB Quorum Rule (G9-QUORUM-001)

RoleRequirement
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.

Two ways to drive the process Portal (recommended): open https://engine.accommodus.com/cockpit.html — the Cockpit page has one-click buttons to submit a CR and advance it through each phase without typing curl commands.
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

Figure 4.4 — Change request end-to-end (CEO perspective)
flowchart TD A["YOU: Submit CR\nPOST /change-request"] --> B["AI: Triage\nrun-phase triage"] B --> C["AI: Solution\nrun-phase solution"] C --> D["AI: Aggregate\nrun-phase aggregate"] D --> E["AI: Cost/Benefit\nrun-phase cost_benefit"] E --> F["YOU: CAB review\nRead output → GO or OBJECT"] F -->|GO| G["AI: Tests\nrun-tests PARTIAL"] G --> H["AI: Developer writes code\nexecute-plan"] H --> I["AI: Verify & close\nverify-and-close"] I -->|gate passes| J["YOU: Deploy\nsystemctl start dm-deploy.service"] J --> K["LIVE ✓"] F -->|OBJECT| L["REJECTED"]
  1. 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:
    curl -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"]
      }'
    The response contains "request_nr": "CHG-NNN". Note this — it is the reference for every subsequent call.
  2. 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.
    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": "triage"}'
    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 via POST /change-request/CHG-NNN/lane/reclassify.
  3. 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.
    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"}'
    You do not need to read or approve the solution output — it feeds the next phase automatically.
  4. 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"}'
  5. 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"}'
  6. CAB review — your decision — this is the only step where you must read the output and make a judgment. Open the CR detail:
    curl https://engine.accommodus.com/change-request/CHG-NNN \
      -H "X-Board-Token: <token>"
    Or open /cockpit.html and click the CR to read the full aggregated plan and cost-benefit summary.

    If you approve (CAB GO):
    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."}'
    If you reject (CAB OBJECT):
      -d '{"to_phase": "cab_object", "actor": "eric-trapman-ceo", "note": "Reason here."}'
    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.
  7. 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"}'
    PARTIAL is the default and sufficient for most changes. FULL runs a deeper suite and spends more API credits.
  8. 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 the PATCH READY board 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.
  9. Host apply — land the patch on the server — SSH to the server and run the host applier. Replace CHG-NNN with the actual reference (e.g. CHG-005):
    cd /root/dm-engine
    ./engine/host/apply_authored_patch.sh CHG-NNN
    This fetches the persisted patch from the board, writes the files, commits as dm-developer-role, rebuilds the engine, and pushes to origin. The /status endpoint will show the new commit when done. See §7.6 for full details and flags.
  10. Verify and close — the reality gate — back in changes.html, advance the CR to Quality Gate and click Run Quality Gate. Or via API:
    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"}'
    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.

Summary: who does what

StepActorWhat they do
SubmitCEO (you)Describe the problem in plain language — no solution required
Triageapp_support (AI)Classifies lane, severity, urgency
SolutionAll impacted roles (AI, isolated)Each role proposes its piece of the solution independently
Aggregatechange_manager (AI)Synthesises role solutions into one plan
Cost/Benefitchange_manager (AI)Computes credits consumed vs value delivered
CABCEO (you)Read the plan and cost — GO or OBJECT. This is your governance gate.
Testssysadmin_devops (AI)Runs read-only acceptance checks
Executedeveloper (AI — claude-opus-4-8)Authors a structured patch and persists it in the board. Does not write files or deploy — separation boundary.
Host applyCEO (you)SSH → apply_authored_patch.sh CHG-NNN: writes files, commits, rebuilds, pushes to origin
Verify & closesysadmin_devops (AI)Reality gate: runs probes, advances to LIVE → CLOSED if all pass
The one rule to remember You (the CEO) act at three points: submit, CAB, and deploy. Everything in between is AI. The change process exists so that the AI does structured work and you hold the governance gates — not the other way around.

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
Part 5

Technology Column

5.1 Infrastructure

Figure 5.1 — Production infrastructure
graph TB subgraph Internet Browser["Browser / curl\nhttps://engine.accommodus.com"] TR["Trade Republic\nwss://api.traderepublic.com"] PX["Perplexity APIs\napi.perplexity.ai"] end subgraph Server["Decision-Machine-prod-01 (VPS)"] Nginx["nginx\n:443 HTTPS\nreverse proxy + TLS"] subgraph Docker["Docker Compose — dm-engine"] App["FastAPI + uvicorn\n127.0.0.1:8000\nengine/api/main.py"] subgraph Files["Persistent volume — engine/model/ + engine/logs/"] GateState["gate_state.json\ngate_authorizations.json"] Session["tr_session.json"] Candidates["candidates.json\nportfolio.json"] Logs["gate_audit.jsonl\nrole_steps.jsonl\nobs_events.jsonl"] end end PG["PostgreSQL 15\nlocalhost:5432\ndatabase: dm"] end Browser --> Nginx --> App App --> PG App --> Files App -.->|Sonar rounds| PX App -.->|Agent rounds| PX TR -.->|WebSocket portfolio| LocalMachine["Local machine\ntr_client.py"] LocalMachine -.->|normalised JSON| App

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/)

All engine/model/ and engine/logs/ files are gitignored. They live on the Docker persistent volume only. Never commit them.
FilePurposeSensitive?
model/gate_state.jsonCurrent gate state (armed, e_stop, execution_mode, auth fields)No
model/gate_authorizations.jsonAuthorization artifact registry (all auth records)No
model/tr_session.jsonTR credentials: phone, PIN, WAF token, session cookies, account IDs⚠️ YES
model/candidates.jsonTrade candidate records (all fields for scoring)No
model/portfolio.jsonCurrent portfolio positionsNo
model/math_bundle.jsonActive governed math bundle version referenceNo
model/runhold_state.jsonCurrent RUN/HOLD mode + active round trackingNo
logs/gate_audit.jsonlAppend-only gate event streamNo
logs/role_steps.jsonlPer-role step log (G-D observability)No
logs/observability_events.jsonlSystem events and alertsNo
logs/runhold_audit.jsonlRUN/HOLD mode change historyNo

5.4 Web Interfaces

URLPageAuth requiredPurpose
/portalportal.htmlToken (client-side)Main operational view: gate indicator, candidate scores, CRs, costs, portfolio
/council.htmlcouncil.htmlToken (client-side)Run rounds, view past transcripts, submit questions
/cockpit.htmlcockpit.htmlToken (client-side)CEO cockpit: roles, models, change queue, one-click advance
/status.htmlstatus.htmlNoneSystem health tile view (roles count, DB, drift, gate, mode)
/index.htmlindex.htmlNoneOriginal AIM grid (static reference view)
/manualmanual.htmlNoneThis 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.

LessonRuleEvidence
L1Author 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
L2Build 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
L3Import convention: bare-name, no engine.*. PYTHONPATH=/app/engine. from engine.board import ... raises ModuleNotFoundError.G-019
L4Never 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
L5Read rules as data. Trading/business rules are READ from scoring_config.yaml at runtime, never written as literals in code.G-007, P1
L6After a host-side developer commit, push from the host. The container does NOT push. Future git pull deploys must be fast-forwards.G-018
L7Rebuild 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
L8External API calls must retry transient failures with exponential backoff + jitter, then fail closed. Never swallow a dead upstream. Do NOT retry 4xx.G-020
Part 6

Trade Republic Integration

6.1 Authentication Flow

Figure 6.1 — TR v2 WebSocket auth sequence
sequenceDiagram participant Script as tr_client.py participant TR_API as TR REST API participant TR_APP as TR Mobile App participant TR_WS as TR WebSocket Script->>TR_API: POST /api/v2/auth/web/login\n{phoneNumber, pin}\nHeaders: x-tr-platform, x-tr-app-version,\nx-tr-device-info (base64), x-aws-waf-token TR_API-->>Script: {processId} TR_API-->>TR_APP: Push notification loop Poll every 2s (max 3 min) Script->>TR_API: GET /api/v2/auth/web/login/processes/{processId} TR_API-->>Script: {status: PENDING} end TR_APP-->>TR_API: User taps Approve Script->>TR_API: GET /api/v2/auth/web/login/processes/{processId} TR_API-->>Script: {status: CONFIRMED} + Set-Cookie (6 cookies) Note over Script: Save all 6 cookies to tr_session.json\nExtract account IDs from JWT payload Script->>TR_WS: WebSocket connect\nHeader: Cookie: tr_session=...; tr_device=...; ... TR_WS-->>Script: connected Script->>TR_WS: sub 1 {"type":"compactPortfolioByType"} Script->>TR_WS: sub 2 {"type":"portfolioStatus"} Script->>TR_WS: sub 3 {"type":"savingsPlans"} TR_WS-->>Script: 1 A {categories:[...positions...]} TR_WS-->>Script: 2 A {status:..., hasInvested:...} TR_WS-->>Script: 3 A {savingsPlans:[...]}

6.2 First-Time Setup

  1. Open https://app.traderepublic.com in Chrome and log in.
  2. Press F12 → Application → Local Storage → https://app.traderepublic.com. Copy the value of awswaf_session_storage.
  3. Run: python engine/tr_client.py --phone +31634132095 --pin 7496 --waf-token <value>
  4. Approve the push notification in the TR app within 3 minutes.
  5. Session saved to engine/model/tr_session.json. Subsequent runs need no arguments.

Required TR v2 Headers

HeaderValueSource
x-tr-platformwebFixed
x-tr-app-version15.7.0Fixed (match TR web app version)
x-tr-device-infobase64-encoded JSON with stableDeviceId, model, browser, os, screen, timezone, etc.Generated from device_id in session
x-aws-waf-tokenvalue of awswaf_session_storage from browser Local StorageOne-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"
}
Known limitation: The 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.
Part 7

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

  1. Read the full transcript in the portal or via GET /decisions/latest. Pay attention to the Devil's Advocate and the Risk Review dissents.
  2. Check the gate stamp on the decision: confirm effective_armed=true was captured at intake.
  3. 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."}'
  4. Execute the trade manually in the TR app (search by ISIN, not ticker).
  5. 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

Call e-stop immediately if: a live order was placed unexpectedly, the gate appears bypassed, or data corruption is suspected.
# 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
L7 invariant: 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

Figure 7.1 — EXECUTE separation boundary
sequenceDiagram participant CEO as CEO (browser) participant Engine as Engine (container) participant Board as Board (DB) participant Host as Host (SSH) CEO->>Engine: POST /run-phase {to_phase: execute} Engine->>Engine: Developer AI authors patch (~90s) Engine->>Board: Persist patch as board row (PATCH READY) Engine-->>CEO: {patch_ready: true, host_command: ...} Note over CEO,Host: SEPARATION BOUNDARY — container stops here CEO->>Host: SSH → apply_authored_patch.sh CHG-NNN Host->>Engine: GET /change-request/CHG-NNN/patch (fetch persisted patch) Host->>Host: Write files to /root/dm-engine Host->>Host: git add -A && git commit (dm-developer-role) Host->>Host: docker compose -f engine/docker-compose.yml up -d --build Host->>Engine: POST /verify-and-close (reality gate) Host->>Host: git push origin HEAD:master

Step-by-step: how to re-run EXECUTE

  1. Open changes.html — go to https://engine.accommodus.com/changes.html and enter your board token → Connect.
  2. 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.
  3. 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.
  4. 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.
  5. Read the result in the timeline — when done, a new developer execute row 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).
  6. SSH to the server and run the host applier — replace CHG-NNN with the actual change reference (e.g. CHG-005):
    cd /root/dm-engine
    ./engine/host/apply_authored_patch.sh CHG-NNN
    This script (see §7.6.1 below) fetches the patch, writes the files, commits, rebuilds, verifies, and pushes to origin. Watch for == done. Confirm /status version_commit == at the end.
  7. 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

StepWhat happensNotes
1. Fetch patchCalls GET /change-request/CHG-NNN/patch on the local engine to retrieve the persisted patch artifact from the boardFails if no patch was persisted (Execute never ran or was held)
2. Write filesCreates/replaces/appends the files declared in the patch under /root/dm-engine/Sandbox-checked: paths must be inside the engine tree
3. Commitgit add -A && git commit attributed to dm-developer-role — the audit trail shows the AI authored this commitLocal commit only — no push yet
4. Rebuilddocker compose -f engine/docker-compose.yml -p dm-engine up -d --build with truthful GIT_COMMIT/EXPECTED_COMMIT stampsRebuild fails → script exits non-zero; run reset-failed before retrying
5. Pushgit push origin HEAD:master — publishes the developer-authored commit to GitHub so origin is never behind the running codeSkipped 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
If the rebuild fails with "no configuration file": the script must be run from /root/dm-engine (not a subdirectory) so that engine/docker-compose.yml resolves correctly. Always cd /root/dm-engine first.
If the service is in a failed state after a bad deploy:
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.

Part 8

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
MethodPathAuthReturns
GET/healthNone{status: "ok"}
GET/statusNoneFull health: roles_count, db_connected, drift, gate state, system_mode, round_active, config_drift, observability
GET/status.htmlNoneHTML status tile page
GET/auth/checkToken{ok: true} if token valid

8.3 Council — Rounds & Roles

Council endpoints
MethodPathBody / ParamsReturns
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/rolesList of 20 role slugs
GET/steps/recent?n=50&role=Recent role step log entries

8.4 Candidates & Scoring

Candidates endpoints
MethodPathBody / ParamsReturns
GET/candidatesAll 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/scoreRanked list with Q, V, status (scored/parked/rejected), gate failures, config_version

8.5 Portfolio & Timeline

Portfolio endpoints
MethodPathBody / ParamsReturns
GET/portfolioCurrent 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/timelinePositions sorted by catalyst_date, with days_to_catalyst and exit_window flag (exit_window = catalyst_date ≥ today + 14d)
GET/portfolio/snapshots?portfolio_id=&limit=50Snapshot index
GET/portfolio/snapshots/{id}Full snapshot record
GET/portfolio/snapshots/{id}/verifyHash verification result
POST/portfolio/snapshotCapture immutable snapshot of current portfolio
POST/portfolio/snapshot/manual{portfolio_id, payload}Snapshot from supplied payload

8.6 Decisions & Ledger

Decisions & Ledger endpoints
MethodPathBody / ParamsReturns
GET/decisionsAll rounds with decisions, newest first
GET/decisions/statsconsecutive_approved, total_approved, target:30, target_met
GET/decisions/latestLatest decision + gate summary (C-G9-01)
GET/decisions/{round_id}/detailTranscript + 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=100All 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
MethodPathBody / Notes
POST/change-request{problem, submitter, impacted_columns, triage?}
GET/change-requestsAll 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}/laneLane 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}/bundleBundle detail (findings + workarounds-to-retire)
GET/change-request/{ref}/patchLatest validated patch artifact (404 if none)
POST/release-bundleAssemble release bundle from open findings
POST/finding{symptom, source, submitter, impacted_columns?, spec_reference?, workaround?}
GET/findingsAll findings (FND-NNN)
GET/finding/{ref}Finding detail
POST/finding/{ref}/status{status, actor, note}

8.8 Gate System

Gate system endpoints
MethodPathBody / Notes
GET/gate/historyAppend-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
MethodPathBody / Notes
GET/system/modeCurrent 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
MethodPathNotes
GET/costs/by-roleCumulative spend since last reset, by role
GET/costs/by-crSpend per change_ref (CHG-* rounds only)
GET/costs/by-roundSpend per round_id (all rounds)
GET/costs/by-nameSpend per /ask name label; unlabelled = "untagged"
POST/costs/reset{actor, confirm: true} — moves display baseline; underlying rows preserved

8.11 Governance & Config

Governance endpoints
MethodPathNotes
GET/governance/config/fingerprintCurrent live config state fingerprint (SHA-256)
GET/governance/config/driftCompare fingerprint to approved baseline; surfaces alert if drifted
POST/governance/config/approve-fingerprint{fingerprint, approved_by} — set new approved baseline
GET/governance/controlsPre-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/governanceRole → owner → enforcement_point → capability_class table
GET/models/preflightPer-role availability against live catalog (fail-closed 503)
GET/models/fit-inputsBest-fit review inputs (separate from availability)
GET/models/gate/complianceCompliance role model validation gate
GET/models/gate/trade-expertTrade expert role model validation gate

8.12 Observability

Observability endpoints
MethodPathNotes
GET/observability/events?n=50 — last N structured events, newest first
GET/observability/alertsCurrent 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
MethodPathNotes
GET/math/bundleActive governed rule bundle version reference
GET/math/bundle/refBundle ref block for embedding in cycle outputs
GET/math/bundle/historyAll bundle activations (append-only), oldest first
POST/math/bundle{bundle_id, bundle_content, actor} — register new bundle; computes SHA-256
GET/artifactsList whitelisted artifacts (RULEBOOK.md, business.md, etc.)
GET/artifacts/{name}Serve artifact file as text/markdown
GET/functionalityAuthoritative Functionality Baseline (AFB) — as-designed reference

8.14 System Model & Question Front Door

System model endpoints
MethodPathNotes
GET/model/dataLive system-model data blob (runtime projection — public, read-only)
GET/sources/analysisInformation 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/contractFrozen Decision Surface contract spec
POST/question{text, intent?, change_fields?} — answer-only or draft change intent
Part 9

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.

Part 10

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

RoleWhat it gathersHas web search
coordinatorOrchestrates the round; assigns research sub-tasksNo
analyst_deskCatalyst timeline, clinical data, trial design, prior readoutsYes (Perplexity deep-research)
risk_reviewFDA precedent, competitive landscape, binary risk assessmentYes (Perplexity sonar-reasoning-pro)
devils_advocateBearish arguments, dilution risk, sector headwindsNo (Claude Opus)
portfolio_managerPrice/volume trend, market cap, IBB sector contextYes (Perplexity pro-search)
trade_expertExit feasibility, ADV, exit window mechanicsNo
complianceRegulatory flags, material non-public riskNo

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.

Why the JSON instruction targets change_manager, not coordinator
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

  1. 7 roles run in parallel — each receives the enrichment question independently via POST /ask.
  2. change_manager aggregates — it receives all 7 role answers and writes a synthesis ending with --- ENRICHMENT JSON --- followed by a JSON object.
  3. Browser parses the JSON blockcandidates.html searches the synthesis text for the separator, extracts the JSON object, and immediately POST /candidates to write the fields into the candidate store.
  4. Score engine re-runs — the leaderboard refreshes. If enough fields are now known, Q and V are computed and the candidate moves from parked to scored.

The 18 scoring fields

FieldTypeUsed inMissing → effect
days_to_catalystnumberV (T_exit), Q (timing)V = UNEVALUABLE → parked
date_confidencehigh/medium/lowQ timing multiplierNo multiplier applied
catalyst_dateISO dateDisplay onlyNo display date shown
adjusted_pos_score0–100Q (PoS bucket, 25%)Q bucket → UNEVALUABLE
expected_magnitude_score0–100Q (magnitude bucket, 25%)Q bucket → UNEVALUABLE
edge_intactness_base0–100Q (edge bucket, 15%)Falls back to x_signal if set
x_signal0–100Q (edge bucket fallback)Edge bucket → UNEVALUABLE
financial_runway_score0–100Q (runway bucket, 10%)Q bucket → UNEVALUABLE
runway_months_past_catalystmonthsHard gate ≥18Gate = UNEVALUABLE → parked
lead_asset_pct_of_value%Hard gate >50Gate = UNEVALUABLE → parked
market_cap_bandmicro/small/midHard gate, R_down fallbackGate = UNEVALUABLE → parked
clean_exit_feasibleboolHard gateGate = UNEVALUABLE → parked
liquidity_exitableboolHard gateGate = UNEVALUABLE → parked
ruin_riskboolImmediate reject if trueDefaults to false
x_crowdedboolQ crowding penaltyDefaults to false (no penalty)
p0–1V (probability of upside)V = UNEVALUABLE → parked
R_upreturn %V (upside return)V = UNEVALUABLE → parked
R_downreturn %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

  1. Open the Leaderboard — navigate to https://engine.accommodus.com/candidates.html. Set your board token at the top.
  2. Find a parked or unenriched candidate — candidates with red status dots or status parked need enrichment. A candidate with enrichment_count = 0 has never been enriched.
  3. Click "▶ Enrich" on the candidate row — this fires an enrichment round for that specific ticker. The button label changes to Running… (~2–3 min).
  4. 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.
  5. Leaderboard refreshes — the candidate's row updates. If the enrichment round produced all required fields, status changes from parked to scored and Q/V scores appear.
  6. 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.
If fields are still UNKNOWN after enrichment
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.

Figure 10.1 — Activity Inspector layout
graph LR A["Left panel
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:

  1. 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.
  2. 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)
    The field display also shows the actual value if it was filled (e.g. days_to_catalyst = 47).
  3. 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
    Each chip shows character count and cost. Click a chip to load that role's response in the right panel.

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
How to diagnose a specific missing field
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 response
5. 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)

MethodPathReturns
GET/roundsAll 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

SymptomLikely causeFix
All fields still UNKNOWN after enrichmentchange_manager didn't write the JSON block; separator may be missing from its responseOpen 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 updateBrowser 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 dotPerplexity rate limit or network errorWait 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 knownAnalyst found the date but expressed it as a range rather than a specific numberRead 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 enrichmentOne or more hard gate fields still UNKNOWN or failingRun GET /candidates/score to see the exact gate failure reason. Update the failing field manually if web sources don't have it.

Decision Machine System Manual · v1.0 · Published 2026-06-22 · Back to top ↑

Active authorization: G9-AUTH-2026-001 · real_money · expires 2026-07-21T23:59:59Z · Renew before expiry via POST /gate/authorize → CAB → POST /gate/arm

Portal: /portal · Council: /council.html · Cockpit: /cockpit.html · API docs: /docs