Skip to main content
Every Ithaca research session follows the same canonical workflow. The agent opens a traced session, researches the market with data tools, proposes a declarative strategy, runs a backtest, subscribes to the run’s progress, and reports the findings to the human. Every step is traced and replayable in the web UI.

The canonical workflow

session_context  →  research (data tools)  →  propose_strategy

                  report  ←  subscribe_run  ←  backtest_run
1

Open a traced session with session_context

The first call is always session_context. It attaches the user’s prompt and an optional title to the MCP trace, opens (or reuses) a persistent research run, and emits a session_open event. Every subsequent tool call in the session is grouped under the same run_id so the web UI lists and replays it as one session.
session_context(
  prompt="Research NVDA — momentum thesis, check insiders, backtest",
  title="NVDA momentum research"
)
If you pass an existing research-run UUID as session_id, future calls attach to that run instead of creating a new one. This lets the web UI start a session and the agent join it.
2

Research with data tools

The agent calls data-layer tools to gather evidence. Each call is traced with a tool_call + tool_result event pair and returns a provenance envelope (source, freshness, as_of, coverage). Typical research calls:
  • market_get_ohlcv(symbol, range, interval) — price history
  • market_screener(sector, limit) — peer screen
  • fundamentals_get(symbol) — metric cards
  • insider_activity(symbol) — SEC Form 4/5 transactions
  • congress_trades(ticker) — STOCK Act disclosures
  • analyst_ratings(symbol) — Wall Street consensus
  • technical_indicators(symbol) — RSI, MACD, Bollinger, ATR
  • vol_realized(symbol) — annualized realized volatility
Use search_skills(query) for progressive discovery rather than loading all 60+ tools into context at once.
3

Propose a strategy with propose_strategy

Once the agent has a thesis, it assembles a declarative StrategySpec and calls propose_strategy. The spec is validated against a closed schema — any free-form or unknown field is rejected. The server stamps id, version, and tenant_id; the agent’s values for those are ignored. The strategy is registered as a draft — nothing deploys.
propose_strategy({
  "spec_version": "1.0",
  "name": "nvda-momentum-60d",
  "universe": {"symbols": ["NVDA"], "source": "prices"},
  "strategy": {"id": "rank_returns", "params": {"lookback": 60, "top": 1}},
  "construction": {"scheme": "rank_weight", "long_only": true, "gross": 1.0},
  "risk": {"max_gross": 1.0, "max_net": 1.0, "max_name_pct": 1.0},
  "execution": {"cost_bps": 5, "algo": "vwap", "rebalance": "monthly@close"},
  "seed": 42
})
Returns {strategy_id, status: "draft", spec}. The strategy_proposed event is traced.
4

Run the backtest with backtest_run

The agent calls backtest_run with the validated spec and a backtest range. The backtest engine runs trusted, audited numpy code over point-in-time price history — no agent-authored code ever executes. The result includes stats (CAGR, Sharpe, max drawdown, win rate), the equity curve, trade log, and a risk-gate decision.
backtest_run(spec=<the validated spec>, range="1y")
For durable, queued backtests (hosted MCP), use run_skill("backtest.run", {spec, range}) instead — it creates a durable run, enqueues a worker job, and returns a run_id immediately.
5

Subscribe to run progress with subscribe_run

For durable runs, the agent polls subscribe_run(run_id, cursor) to follow progress. Each call returns trace events after the supplied cursor. The agent repeats this until the run reaches a terminal state (succeeded, failed, cancelled).
subscribe_run(run_id="...", cursor=-1)
6

Report findings to the human

The agent synthesizes the research and backtest results into a report for the human. This is pure narrative — the agent writes its summary to the chat. The human then decides whether to promote the strategy to paper trading from the web UI.
The agent cannot promote a strategy to paper trading. That is a human-only action, enforced at the code level. The agent can only propose and backtest.

Server instructions

When the agent connects to the Ithaca MCP server, it receives these instructions as part of the server’s instructions field in the initialize response. The agent is expected to follow them:
You are connected to Ithaca, a quant research and paper-trading observability platform.

CANONICAL WORKFLOW — follow this for every research request:
1. Call session_context first with the user's prompt. This opens a traced research session.
2. Research the market using data tools (market_get_ohlcv, fundamentals_get, insider_activity,
   congress_trades, analyst_ratings, technical_indicators, vol_realized, etc.).
   Use search_skills to discover tools progressively — do not assume the full catalog.
3. When you have a thesis, call propose_strategy with a declarative StrategySpec.
   The spec is a closed schema — unknown fields are rejected. Do not include code.
4. Call backtest_run (or run_skill with skill_id "backtest.run") to test the strategy.
5. If the backtest is durable, call subscribe_run repeatedly to follow progress.
6. Report the findings to the human: thesis, evidence, strategy, backtest stats (Sharpe,
   CAGR, max drawdown), and a recommendation.

CONSTRAINTS:
- You can READ data and PROPOSE strategies. You cannot deploy, promote, or move money.
  Paper-trading promotion is a human action in the web UI.
- Every tool call is traced. The human watches your research unfold live in the web UI.
- Strategies are declarative specs, not code. Never attempt to submit executable code.
- All data is tenant-scoped. You can only see data belonging to the authenticated tenant.

PROVENANCE:
- Every data tool returns a provenance envelope: source, freshness, as_of, coverage.
- If freshness is "stale" or coverage is partial, note this in your report.
- Never fabricate data. If a tool returns an error, report the error and retry or pivot.

Example: NVDA research session

Here is a full NVDA research session showing the agent’s tool calls and expected outputs.

1. Open the session

// tool call
session_context(
  prompt="Research NVDA — momentum thesis, check insiders and congress trades, propose a 60-day momentum strategy, backtest it over 1 year",
  title="NVDA momentum research"
)

// expected output
{
  "status": "ok",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "prompt_captured": true
}
The session_open event is traced. The web UI now shows a new research session.

2. Pull price history

// tool call
market_get_ohlcv(symbol="NVDA", range="1y", interval="1d")

// expected output (abbreviated)
{
  "data": {
    "symbol": "NVDA",
    "points": [
      {"d": "2024-06-03", "o": 122.57, "h": 124.12, "l": 121.80, "c": 122.90, "v": 4.2e8},
      {"d": "2024-06-04", "o": 123.01, "h": 125.50, "l": 122.88, "c": 124.30, "v": 3.8e8},
      ...
    ],
    "quote": {"symbol": "NVDA", "price": 178.40, "asOf": "2025-06-02T20:00:00Z"}
  },
  "provenance": {
    "source": "doomberg-archive",
    "freshness": "delayed",
    "as_of": "2025-06-02T20:00:00Z",
    "coverage": {"requested": 252, "resolved": 252, "missing": []}
  }
}

3. Screen peers

// tool call
market_screener(sector="Technology", limit=20)

// expected output (abbreviated)
{
  "data": {
    "title": "Technology Screener",
    "rows": [
      {"symbol": "NVDA", "name": "NVIDIA Corp", "mktCap": 4.3e12, "return1y": 0.45},
      {"symbol": "AMD",  "name": "Adv. Micro Devices", "mktCap": 2.6e11, "return1y": 0.12},
      ...
    ],
    "count": 20
  },
  "provenance": {"source": "doomberg-archive", "freshness": "delayed", ...}
}

4. Check fundamentals

// tool call
fundamentals_get(symbol="NVDA")

// expected output (abbreviated)
{
  "data": {
    "symbol": "NVDA",
    "cards": [
      {"metric": "P/E (TTM)", "value": 68.4, "sectorMedian": 35.2},
      {"metric": "Revenue Growth YoY", "value": 2.08, "sectorMedian": 0.12},
      {"metric": "Gross Margin", "value": 0.75, "sectorMedian": 0.48},
      ...
    ]
  },
  "provenance": {...}
}

5. Check insider activity

// tool call
insider_activity(symbol="NVDA", limit=20)

// expected output (abbreviated)
{
  "data": {
    "symbol": "NVDA",
    "transactions": [
      {"filingDate": "2025-05-15", "insider": "Jen-Hsun Huang", "type": "sell", "shares": 60000, "value": 10.7e6},
      ...
    ],
    "netSummary": {"buys": 0, "sells": 12, "netShares": -480000}
  },
  "provenance": {...}
}

6. Check congress trades

// tool call
congress_trades(ticker="NVDA", limit=20)

// expected output (abbreviated)
{
  "data": {
    "trades": [
      {"member": "Rep. Nancy Pelosi", "transactionDate": "2024-11-22", "type": "buy", "amount": "$1M-$5M"},
      ...
    ],
    "highlights": ["Pelosi buy 30 days before earnings announcement"]
  },
  "provenance": {...}
}

7. Propose the strategy

// tool call
propose_strategy({
  "spec_version": "1.0",
  "name": "nvda-momentum-60d",
  "universe": {"symbols": ["NVDA"], "source": "prices"},
  "strategy": {"id": "rank_returns", "params": {"lookback": 60, "top": 1}},
  "construction": {"scheme": "rank_weight", "long_only": true, "gross": 1.0},
  "risk": {"max_gross": 1.0, "max_net": 1.0, "max_name_pct": 1.0},
  "execution": {"cost_bps": 5, "algo": "vwap", "rebalance": "monthly@close"},
  "seed": 42
})

// expected output
{
  "strategy_id": "stg_a1b2c3d4e5f6",
  "status": "draft",
  "spec": {
    "spec_version": "1.0",
    "name": "nvda-momentum-60d",
    "id": "stg_a1b2c3d4e5f6",
    "version": 1,
    ...
  }
}

8. Run the backtest

// tool call (in-process)
backtest_run(spec=<validated spec>, range="1y")

// expected output (abbreviated)
{
  "data": {
    "stats": {
      "cagr": 0.38,
      "sharpe": 1.42,
      "maxDrawdown": -0.18,
      "winRate": 0.58,
      "trades": 12
    },
    "equityCurve": [...],
    "tradeLog": [...],
    "risk": {"ok": true, "gross": 1.0, "net": 1.0}
  },
  "provenance": {"source": "computed", "freshness": "live", ...}
}
For a durable (queued) backtest over hosted MCP:
// tool call
run_skill(skill_id="backtest.run", input={"spec": <validated spec>, "range": "1y"})

// expected output (immediate)
{
  "run_id": "r_9f8e7d6c5b4a",
  "status": "queued",
  "status_url": "/api/runs/r_9f8e7d6c5b4a",
  "estimated_profile": "backtest",
  "job_id": "j_1a2b3c4d",
  "approval_required": false
}

9. Subscribe to progress (durable runs)

// tool call
subscribe_run(run_id="r_9f8e7d6c5b4a", cursor=-1)

// expected output (abbreviated)
{
  "run_id": "r_9f8e7d6c5b4a",
  "cursor": 7,
  "events": [
    {"seq": 1, "type": "run_transition", "data": {"from": "draft", "to": "queued"}},
    {"seq": 2, "type": "run_transition", "data": {"from": "queued", "to": "running"}},
    {"seq": 3, "type": "tool_call", "data": {"tool": "backtest.run", ...}},
    {"seq": 4, "type": "tool_result", "data": {"stats": {"sharpe": 1.42, ...}}},
    {"seq": 5, "type": "run_transition", "data": {"from": "running", "to": "succeeded"}}
  ]
}

10. Report to the human

The agent synthesizes a narrative report. Example:
NVDA Momentum Research — Summary Thesis: NVDA shows strong 60-day momentum (1Y return +45%, well above sector median +12%). Revenue growth YoY of +208% and gross margin of 75% support the momentum fundamentally. Evidence:
  • Price: NVDA returned +45% over the past year vs. Technology sector median +12%.
  • Fundamentals: P/E of 68 is rich, but revenue growth (208%) and margin (75%) are best-in-class.
  • Insiders: Net selling over the past 90 days (-480k shares). This is a caution flag but consistent with planned 10b5-1 sales.
  • Congress: One notable Pelosi buy 30 days before the last earnings beat.
Strategy: 60-day rank-momentum, monthly rebalance, long-only, 100% gross, 5 bps cost. Backtest (1Y): CAGR 38%, Sharpe 1.42, max drawdown -18%, win rate 58%, 12 trades. Risk gate passed. Recommendation: The momentum thesis is supported by fundamentals. Insider selling is a watch item. The backtest is strong but the drawdown is meaningful. I recommend promoting to paper trading to test live behavior — you can do this from the Ithaca web UI.

The run state machine

The control plane owns the run lifecycle. The agent can create and observe runs but cannot drive money-path transitions.
draft → queued → running → succeeded
                         ↘ failed
                         ↘ timeout
                         ↘ cancelled

# paper deployment lifecycle (human-only):
queued/running/succeeded → promoted → live ⇄ paused → retired
TransitionWhoHow
draft → queuedAgent or humanrun_skill / POST /api/runs
queued → runningWorkerautomatic (job claimed)
running → succeeded/failed/timeoutWorkerautomatic (job completes)
→ cancelledAgent or humancancel_run / POST /api/runs/{id}/cancel
→ promotedHuman onlyweb UI button → POST /api/runs/{id}/promote
promoted → liveHuman onlyweb UI → activate
live ⇄ pausedHuman onlyweb UI → pause / resume
→ retiredHuman onlyweb UI → retire

MCP Connection

Transport options, the initialize handshake, and tool discovery.

Starter Prompt

The starter_research prompt template and ready-to-paste example prompts.

MCP Tools

Browse the full 60+ tool catalog.

StrategySpec

The declarative strategy schema and how backtests work.