Install
openclaw skills install investorclawDeterministic-first portfolio analyzer — holdings, performance, Sharpe + Sortino, FRED yield curves, bond duration, sector breakdowns, scenario rebalancing — via MCP-HTTP. Backed by ic-engine and clio.
openclaw skills install investorclawA deterministic-first portfolio analyzer that does real money math: holdings snapshots, performance metrics, Sharpe ratios, FRED yield curves, bond duration, sector breakdowns, scenario rebalancing. Backed by ic-engine (Python, FINOS CDM 5.x compliant).
This skill follows the compose-x-mcp-services convention (2026-05-01 RFC; see RFC-v0.1.md in this bundle). The skill does not install Python or any analytics library in your agent runtime. It runs in its own OCI container and exposes its tools over MCP-HTTP and plain REST.
Thirteen MCP tools (also available as plain HTTP REST endpoints):
| Tool | Purpose |
|---|---|
portfolio_ask | Primary tool — every portfolio question. Data is auto-loaded; just ask. |
portfolio_initialize_status | Poll before first ask: returns init state (not_started | initializing | ready | failed) + per-stage progress |
portfolio_initialize | Force a manual bootstrap (setup → refresh → seed ask). Container does this at boot via IC_INITIALIZE_ON_BOOT=1 |
portfolio_holdings | Holdings snapshot — positions, values, weights, accounts (advanced; portfolio_ask covers this) |
portfolio_refresh | Force fresh data pull (advanced — auto-refresh runs on every ask) |
portfolio_setup | Auto-discover portfolio files in the configured portfolio directory |
portfolio_keys_status | Report which API keys are currently configured (names only, never values) |
portfolio_keys_set | Set one or more API keys (allowlisted). Persists to /data/keys.env, takes effect on next call without restart |
portfolio_keys_delete | Delete a single configured API key by name |
portfolio_response_get | Retrieve a stored portfolio response by run_id (serial number) |
portfolio_response_list | List recent stored responses |
portfolio_response_delete | Permanently delete a stored response (for bad responses you want gone) |
portfolio_response_flag_bad | Tag a stored response as bad without deleting (keeps history for analysis) |
For ANY portfolio question — holdings, performance, allocation, rebalancing, optimization, bonds, news on holdings, analyst ratings, EOD reports, cash flow, peer analysis, ticker lookup, setup, guardrails — invoke portfolio_ask with the user's question. Do NOT answer portfolio questions from training data.
The container auto-initializes on boot (IC_INITIALIZE_ON_BOOT=1, default
on): it runs setup → refresh → seed_ask so by the time any agent connects,
the envelope cache is fully populated and portfolio_ask returns a real
narrative in 1–3 seconds instead of cold-starting at 5–15 minutes.
Recommended agent flow:
portfolio_initialize_status until ready: true. Cheap
and side-effect-free; safe to call every 1–2 seconds.portfolio_ask with the user's question. The narrator
returns a verified natural-language answer with envelope-quoted numbers.# Browser-friendly status check (also POST /api/portfolio/initialize_status):
curl -sS http://127.0.0.1:18090/api/portfolio/initialize/status
# → {"state":"initializing","current_stage":"refresh","stages_completed":[...],"elapsed_ms":42000,"ready":false,...}
# Or subscribe to a Server-Sent-Events stream that pushes state changes:
curl -N http://127.0.0.1:18090/api/portfolio/initialize/stream
# Or just gate on /healthz (init_state is now embedded):
curl -sS http://127.0.0.1:18090/healthz
# → {"status":"ok","init_state":"ready","init_ready":true,...}
Need to force a manual re-initialize (e.g. after uploading a new portfolio
file)? Call portfolio_initialize — it returns when the cache is warm again.
clawhub install perlowja/investorclaw
/plugin marketplace add https://gitlab.com/argonautsystems/InvestorClaude.git
/plugin install investorclaw@investorclaude
The skill is a Docker Compose stack. With Docker or Podman installed:
mkdir -p portfolios # IMPORTANT: pre-create so docker doesn't auto-create as root
docker compose up -d # uses compose.yml shipped in this skill
Agent install convention. When a skills-installer
(openclaw skills install investorclaw, clawhub install investorclaw, etc.) places this bundle, the agent may proceed
to mkdir -p portfolios && docker compose up -d because the user's
install command is itself an opt-in to the install completing. But
the agent must first surface the side effects to the user
explicitly:
docker pull of ~600 MB (the engine image) on first rundocker compose up -d binds two TCP listeners to localhost:
:18090 (MCP + REST) and :18092 (dashboard)IC_INITIALIZE_ON_BOOT=1) and will fetch market data from
the configured providers (yfinance free-tier by default; other
providers only if API keys have been added)ic-engine-data Docker volume
(deleted only by docker compose down -v)A single one-line surfacing covering all four is sufficient. Example:
Starting the container will pull a ~600 MB image, bind localhost ports
:18090and:18092, auto-init against yfinance, and persist state in a Docker volume. Proceeding…
The user retains the ability to interrupt (Ctrl-C, "no don't", etc.). Don't ask a yes/no question by default — that's friction the user has already cleared by typing the install command. Do ask when one of these conditions holds:
compose.yml first.ic-engine container is already running on the host
with a different image tag — confirm before disrupting.--dry-run or --no-start to the install
command (if the skills installer surfaces those flags).The first command (mkdir -p portfolios) is load-bearing. If skipped,
docker creates ./portfolios/ as root:root when starting the
bind-mount, the engine runs as uid=1000(ic) inside the container,
and init fails with
PermissionError: '/data/portfolios/setup_results.json' and the
container goes into init_state=failed. Pre-creating the directory
as the host user sidesteps the docker bind-mount UID inheritance
quirk.
The compose pulls ghcr.io/argonautsystems/ic-engine:4.1.34-cpu (publicly hosted, no auth) and runs it on localhost:18090 (MCP + REST) and localhost:18092 (dashboard).
Install Docker Desktop or Docker Engine for your platform — the user should run the install themselves rather than have an agent execute the command. Pointers (verify with each OS's current docs at https://docs.docker.com/engine/install/ before running):
| OS | Suggested path |
|---|---|
| macOS | Docker Desktop: https://docs.docker.com/desktop/install/mac-install/ (Homebrew users: brew install --cask docker) |
| Debian/Ubuntu | Follow the official guide: https://docs.docker.com/engine/install/debian/ or https://docs.docker.com/engine/install/ubuntu/ |
| Fedora/RHEL | https://docs.docker.com/engine/install/fedora/ or https://docs.docker.com/engine/install/rhel/ |
| Windows | Docker Desktop with WSL2 backend: https://docs.docker.com/desktop/install/windows-install/ |
| Podman alternative | podman compose up -d is a drop-in replacement once Podman is installed (most distros ship it) |
After install, verify with docker --version then run the compose-up
command above.
For agent operators: prefer surfacing these install URLs to the
end user rather than running package-manager install commands directly
through your shell tool. Docker installation typically requires sudo
and adds the user to the docker group — operations that benefit from
explicit user consent.
until curl -sf http://localhost:18090/healthz > /dev/null 2>&1; do sleep 1; done
echo "ic-engine ready"
The first cold-start takes 5-10 seconds (image extract + Python import). Subsequent restarts are <2s.
After docker compose up -d the container goes through an auto-init
sequence (IC_INITIALIZE_ON_BOOT=1) that warms the envelope cache before
your agent talks to it. Expect this timeline on a fresh install:
| Phase | Time | What's happening | What you'll see |
|---|---|---|---|
| Image extract | 5–30 s | First-time pull of ic-engine:4.1.34-cpu (~600 MB) | docker compose progress bars |
| Bridge boot | 2–3 s | FastMCP server binds :18090, dashboard binds :18092 | /healthz returns 200, init_state: not_started |
portfolio_setup | 1–60 s | Auto-discover portfolio files in ./portfolios/ | init_state: initializing, current_stage: setup |
portfolio_refresh | 30–120 s | Pull quotes / analyst / news / FRED yields for each symbol | init_state: initializing, current_stage: refresh |
seed_ask | 5–60 s | Run a primer ask so the cache is warm | init_state: initializing, current_stage: seed_ask |
| Ready | — | All sections cached, portfolio_ask returns in 1–3 s | init_state: ready, init_ready: true |
Total cold-start budget: ~60-200 s for a 100-position portfolio, ~5-15 minutes for a 200+ position portfolio without paid quote keys. Watch progress via:
curl -sS http://127.0.0.1:18090/api/portfolio/initialize/status | jq
# or stream:
curl -N http://127.0.0.1:18090/api/portfolio/initialize/stream
The container does not prompt interactively. It surfaces what it needs through structured responses:
A portfolio file. If ./portfolios/ is empty, every portfolio_ask
call returns: "No portfolio file found … please add CSV/Excel/PDF
files to your portfolios directory." Drop a broker export from
Schwab / Fidelity / Vanguard / UBS / ETrade / Robinhood (CSV/XLS/PDF/screenshot)
into the bind-mounted ./portfolios/ folder, then call
portfolio_setup to ingest it.
An LLM provider key for narrative synthesis. Without one, the
engine still runs the deterministic pipeline (numbers are correct)
but the narrator returns a stub catalog blurb instead of a real
prose answer. The container ships pre-configured to use Together AI
(google/gemma-4-31B-it), so all you need is a TOGETHER_API_KEY.
Set it with:
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/keys_set \
-H 'Content-Type: application/json' \
-d '{"keys": {"TOGETHER_API_KEY": "tgp_v1_..."}}'
Or drop into the dashboard at http://localhost:18092/ and paste it into the Settings tab.
Optional: data-provider keys for richer / faster results on larger portfolios (see Optional configuration → Which keys to obtain (by portfolio size) below). The engine works key-less in degraded mode (yfinance-only, rate-limited).
| Size | Required | Recommended | Why |
|---|---|---|---|
| ≤ 50 symbols | TOGETHER_API_KEY (narrative) | — | yfinance handles quotes/history at this scale; one key covers narrative |
| 50–200 symbols | TOGETHER_API_KEY | FINNHUB_KEY (free 60/min) + NEWSAPI_KEY (free 100/day) | Real-time quotes + analyst + per-symbol news without yfinance throttle |
| 200+ symbols | TOGETHER_API_KEY + MASSIVE_API_KEY (Polygon, paid) | FINNHUB_KEY + MARKETAUX_API_KEY (free 100/day) + FRED_API_KEY (free, registration) + ALPHA_VANTAGE_KEY (free 25/day) | Yahoo's anonymous query1 endpoint rate-limits globally on 200+ symbols under barrage; Polygon is required, the rest fill analyst + news + yields |
Why TOGETHER_API_KEY is the only hard requirement for narrative:
google/gemma-4-31B-it has good quality for portfolio
narrative + ~100 tok/s throughputSign-up links (all have free tiers):
| Provider | URL | Free-tier limit |
|---|---|---|
| Together AI | https://api.together.ai/settings/api-keys | $1 free credits |
| Finnhub | https://finnhub.io/register | 60 calls/min |
| Polygon (Massive) | https://polygon.io/dashboard/api-keys | paid only |
| MarketAux | https://www.marketaux.com/account/dashboard | 100 calls/day |
| NewsAPI | https://newsapi.org/register | 100 calls/day |
| FRED | https://fred.stlouisfed.org/docs/api/api_key.html | unlimited (registration only) |
| Alpha Vantage | https://www.alphavantage.co/support/#api-key | 25 calls/day |
The TOGETHER_API_KEY is the only one that's genuinely required.
Everything else degrades gracefully.
Once init_state: ready and a portfolio is loaded, the very first
portfolio_ask call returns a response shaped like:
{
"exit_code": 0,
"narrative": "I have holdings summary data in the envelope.\n- bond_pct: 26.76\n- bond_value: 705646.57\n- cash_pct: 1.69\n- equity_pct: 71.55\n- equity_value: 1886470.25\nTop holding symbols: MSFT, NVDA, SCHB, GOOG, AAPL, ...",
"ic_result": {
"hmac": "75ca79c...",
"engine_version": "2.5.2",
"command": "ask",
"run_id": "299d36b0-..."
}
}
The narrative field is the agent-facing answer. The ic_result
contains the HMAC signature that proves the response came from the
deterministic engine (not LLM-fabricated).
If you see "is a general finance concept. ic-engine is portfolio-specific" in the narrative for a question that obviously is about your portfolio, you're on a pre-v4.1.25 image — pull the latest:
docker compose pull && docker compose up -d
If your runtime has a native MCP client, register the server:
URL: http://127.0.0.1:18090/mcp
Transport: streamable-http
Auth: none (localhost only)
Per-runtime CLI:
| Runtime | Command |
|---|---|
| zeroclaw | Add [[mcp.servers]] with name = "ic-engine", url = "http://127.0.0.1:18090/mcp", transport = "http" to ~/.zeroclaw/config.toml |
| openclaw | openclaw mcp set ic-engine '{"url":"http://127.0.0.1:18090/mcp","transport":"streamable-http"}' |
| hermes | hermes mcp add ic-engine --url http://127.0.0.1:18090/mcp |
| claude code | Add to ~/.claude/mcp_servers.json per Claude Code docs |
Then call tools by name (portfolio_ask, portfolio_holdings, etc.) via your runtime's tool-use API.
Equivalent endpoints exist at /api/portfolio/*. Use your runtime's shell or HTTP tool:
# Ask any portfolio question
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/ask \
-H 'Content-Type: application/json' \
-d '{"question": "What is in my portfolio?"}' \
--max-time 120
# Other endpoints (no body needed)
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/holdings -H 'Content-Type: application/json' -d '{}'
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/refresh -H 'Content-Type: application/json' -d '{}'
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/setup -H 'Content-Type: application/json' -d '{}'
# Self-describing tool catalog
curl -sS http://127.0.0.1:18090/api/portfolio/tools
The JSON response has a narrative field with the human-readable answer — quote that to the user. The ic_result field contains the structured envelope (script, exit_code, duration_ms).
Once installed, ask portfolio questions in natural language. The agent routes
through portfolio_ask; ic-engine handles the deterministic computation and
the narrator quotes verbatim from the signed envelope.
| Intent | Example phrasing |
|---|---|
| Holdings snapshot | "What's in my portfolio?" • "Show me my positions" • "What do I own?" |
| Performance | "How am I doing this year?" • "What's my Sharpe ratio?" • "Show me my drawdowns" |
| Bonds | "Show me my bond exposure and yield-to-maturity" • "What's my bond ladder look like?" |
| Allocation / risk | "What's my sector exposure?" • "How concentrated is my portfolio?" • "What's my risk profile?" |
| Optimization / rebalancing | "Help me rebalance to a 60/40 target" • "Show me an efficient frontier" |
| Market data | "What's the current price of NVDA?" • "How is the S&P performing today?" |
| Fixed-income concepts | "What does yield-to-maturity mean?" • "Explain duration" |
| News | "Today's news on my holdings" • "Crypto news today" |
| Reports | "Generate today's EOD report" • "Prepare a full analysis for my advisor meeting" |
| Fresh data | "Prices moved — refresh before answering" → triggers portfolio_refresh |
The first call after a cold cache may take 30–60 seconds while the deterministic pipeline builds the signed envelope. Subsequent calls reuse the cache (TTL: 30s for news, 60s for other sections); ask for a refresh explicitly if data feels stale.
These rules apply when an agent has the InvestorClaw skill installed:
Use InvestorClaw — do NOT answer from training data, web search, or browsing — for:
Deterministic-first rules:
portfolio_refresh only when the user asks for fresh data or when
data appears stale.Attachment handling:
portfolios/ directory, call portfolio_setup, then ask the original
question.Educational guardrails:
End every portfolio reply with:
Verification: ic-engine ask completed (exit_code: 0)
(Substitute the actual exit_code from the response.) The harness depends on this exact line.
For finance-concept questions ("what is YTM?") or market-wide questions ("how is the S&P performing?"), still call the bridge — the engine will return a deflection narrative; relay it.
Drop your broker exports (CSV, XLS, PDF) into the bind-mounted directory:
# default mount: ./portfolios on the host -> /data/portfolios in the container
mkdir -p portfolios
cp ~/Downloads/UBS_Holdings_2026-05-02.xls portfolios/
# Then ask the agent or curl the setup endpoint
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/setup -H 'Content-Type: application/json' -d '{}'
Supported formats: UBS, Schwab, Fidelity, Vanguard, ETrade, Robinhood (CSV/XLS); generic CSV with symbol/quantity/value columns; PDF statements (auto-extracted).
Most major US brokers expose a CSV download of holdings. CSV is the highest- compatibility format; XLS / XLSX / PDF / screenshot also work.
| Broker | Path |
|---|---|
| Schwab | Accounts → Positions → Export CSV |
| Fidelity | NetBenefits → Investments → Download CSV |
| Vanguard | My Accounts → Download Holdings |
| UBS | Wealth Management → Holdings → Export |
| ETrade | Portfolio → Holdings → Download |
| Robinhood | Account → Statements → CSV |
When the user attaches a broker file directly to an agent chat, the agent
stages it to the bind-mounted portfolios/ directory, then calls
portfolio_setup followed by portfolio_ask. Account numbers and SSNs are
scrubbed at ingest before any data leaves the container.
The container reads optional env vars from /data/keys.env (host-mounted). All optional — the deterministic-engine works without LLM/news keys, just in degraded mode (no narrative synthesis, no live news).
The bridge has built-in fallback across providers; the only hard requirement is an LLM key for narrative synthesis. Below that, your choice depends on portfolio size.
Small (≤50 symbols) — yfinance-only is fine:
TOGETHER_API_KEY (or any LLM): required for narrativeMedium (50–200 symbols) — add Finnhub:
TOGETHER_API_KEY: LLM narrativeFINNHUB_KEY: real-time quotes + analyst ratings (60/min, free)NEWSAPI_KEY (optional): per-symbol news (100/day free)Large (200+ symbols) — Polygon (Massive) is required:
TOGETHER_API_KEY: LLM narrativeMASSIVE_API_KEY (Polygon): paid, un-rate-limited quotes + historyFINNHUB_KEY: analyst ratings + general/forex/crypto/merger newsMARKETAUX_API_KEY (optional): broader news with category filtersFRED_API_KEY (optional): Treasury yield curve (Treasury.gov fallback runs without)ALPHA_VANTAGE_KEY (optional): supplemental EOD prices (25/day free)Why: Yahoo's anonymous query1 endpoint rate-limits globally (HTTP 429) on
200+ symbol portfolios under barrage load. Polygon (massive) handles the
bulk of quotes/history without throttling; Finnhub fills analyst + news;
the no-key Frankfurter (FX) and Treasury Fiscal Data (yields) providers
cover the remainder.
| Key | Purpose | Cost note |
|---|---|---|
TOGETHER_API_KEY | LLM narrative synthesis (Together google/gemma-4-31B-it) | serverless, fleet default |
MASSIVE_API_KEY | Polygon quotes + history (200+ symbol portfolios) | paid, un-rate-limited |
FINNHUB_KEY | Real-time quotes + analyst ratings + category news | 60/min free |
MARKETAUX_API_KEY | Financial news with broader filters than NewsAPI | 100/day free |
NEWSAPI_KEY | Per-symbol news (US sources only) | 100/day free |
ALPHA_VANTAGE_KEY | Supplemental EOD prices | 25/day free |
FRED_API_KEY | FRED yield curve | free, registration required |
OPENAI_API_KEY | Alternative LLM (GPT-4o, GPT-5) | paid |
| Provider | Coverage |
|---|---|
| yfinance | Quotes, history, news, analyst (rate-limited; safety-net only on 200+ portfolios) |
| Frankfurter | FX spot rates (EUR/USD, USD/JPY, etc.) — ECB-sourced |
| Treasury Fiscal Data | US Treasury yield curve fallback when FRED_API_KEY missing |
The agent can set keys directly via the running container, no /data/keys.env
edit required. Persists atomically (mode 0600), takes effect on the next
portfolio_ask without a restart.
# What's configured?
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/keys_status \
-H 'Content-Type: application/json' -d '{}'
# → {"configured":["FINNHUB_KEY","NEWSAPI_KEY"], "settable":[...], "missing":[...]}
# Set one or more keys
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/keys_set \
-H 'Content-Type: application/json' \
-d '{"keys": {"TOGETHER_API_KEY": "tgp_v1_...", "FRED_API_KEY": "..."}}'
# → {"configured":["FRED_API_KEY","TOGETHER_API_KEY"], "rejected":[], "deleted":[]}
# Remove a key
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/keys_delete \
-H 'Content-Type: application/json' -d '{"name": "OPENAI_API_KEY"}'
The same operations are available as MCP tools: portfolio_keys_status,
portfolio_keys_set, portfolio_keys_delete. Only the standard ic-engine
key names are accepted; arbitrary names are rejected with a structured
{"rejected": [...], "settable": [...]} response.
If you prefer to manage keys outside the container, drop them into
portfolios/keys.env on the host (the bind-mounted location), one
KEY=VALUE per line:
TOGETHER_API_KEY=tgp_v1_...
FINNHUB_KEY=...
NEWSAPI_KEY=...
The container reads from /data/keys.env at boot.
InvestorClaw uses two LLM roles when answering: narrative (synthesizes the signed envelope into prose) and validator (checks the narrative against the envelope for fabrication and number-preservation). The recommended model mix depends on your runtime.
The agent's own LLM does both roles — no external API key required.
This split is cost-shaped: cheap model on the long output, smart model on the short safety check. Total session cost on a 100-position portfolio typically lands well under $0.01.
Anthropic on the claws stack — three paths, two of them paid: since 2026-04-04 your Anthropic OAuth subscription no longer covers third-party-tool usage. To use Anthropic models on a claws-agent runtime you need either (a) Anthropic's discounted "extra usage bundles" added to your subscription, or (b) a direct Anthropic API key. Routing OAuth-subscription tokens to a claws-agent without one of those is a ToS violation per Anthropic's Apr 3 announcement.
Even with paid credits, Anthropic isn't cost-competitive with Together for InvestorClaw narrative synthesis (~10–50× the per-token cost). On our own fleet infrastructure we don't deploy Anthropic for these runtimes; end-users should weigh ToS, cost, and quality before opting into it.
Bring a non-Anthropic provider via TOGETHER_API_KEY (or equivalent).
Fleet defaults:
google/gemma-4-31B-it — serverless
tier, ~100 tok/s, ~$0.0008 / 1 K tokens, ships as the container default.MiniMaxAI/MiniMax-M2 —
larger context, but moved off Together's serverless tier in 2026-05
and now requires a paid dedicated endpoint. Use only if you've
provisioned that endpoint.gemma4:e4b on host — zero cloud cost,
GPU-bound, no key required.To switch the narrative model, set INVESTORCLAW_NARRATIVE_MODEL in
portfolios/keys.env (e.g. INVESTORCLAW_NARRATIVE_MODEL=MiniMaxAI/MiniMax-M2
once you have a dedicated endpoint configured at Together).
The container reads it on next call without restart.
Stays on your machine:
portfolios/Sent to the configured LLM provider for narrative synthesis:
Never sent anywhere:
InvestorClaw never executes trades, never moves money, never accesses brokerage APIs for transactions. Output is educational only.
# Health check
curl -sS http://127.0.0.1:18090/healthz
# → {"status":"ok","ic_engine_bin_found":true,"portfolio_dir":"/data/portfolios","portfolio_dir_exists":true,"reports_dir":"/data/reports"}
# Smoke test the tool catalog
curl -sS http://127.0.0.1:18090/api/portfolio/tools | python3 -m json.tool
# Smoke test a real question
curl -sS -X POST http://127.0.0.1:18090/api/portfolio/ask \
-H 'Content-Type: application/json' \
-d '{"question": "What is in my portfolio?"}' \
--max-time 120
If your agent supports compliance testing, vendor test_mcp_compliance.py
from the mnemos-os/mcp-contracts GitHub repository into your project, then run:
python3 test_mcp_compliance.py --url http://127.0.0.1:18090/mcp
The container exposes a single-page HTML dashboard on port :18092:
open http://localhost:18092/ # macOS
xdg-open http://localhost:18092/ # Linux
start http://localhost:18092/ # Windows
Tabs cover: Holdings · Performance · Bonds · Analyst · News · Cashflow · Optimize · Synthesis · What-changed · Tax · Scenarios · Peer · Reports · Settings · About.
The dashboard reads the same signed envelope ic-engine produces for
portfolio_ask, so metrics stay in sync. Use it for visual review of
holdings / performance, or as a fallback interface when MCP integration
is flaky.
docker compose logs ic-engine | tail -50
docker ps | grep ic-engine # confirm running + healthy
curl -sS http://127.0.0.1:18090/healthz
If healthz returns {"init_state":"failed", ...}, check the init_error
field for the engine's exact failure message.
portfolios/ (the host bind mount).curl -X POST http://127.0.0.1:18090/api/portfolio/setup -d '{}'curl -X POST http://127.0.0.1:18090/api/portfolio/ask -d '{"question":"what's in my portfolio?"}'The agent can stage attached files to portfolios/ directly when the user
sends them in chat.
Keys are optional. The deterministic-engine works key-less in degraded mode (no narrative synthesis, no live news, yfinance-only quotes). To check what's configured:
curl -X POST http://127.0.0.1:18090/api/portfolio/keys_status -d '{}'
Set the missing key via the REST endpoint shown in Optional configuration.
Only happens on a cold cache for portfolios with 200+ positions. The
container runs IC_INITIALIZE_ON_BOOT=1 by default — initialization runs
at container start, so by the time the agent connects, the cache is warm.
If you disabled that env var, expect cold-start latency on first ask.
Check init progress: curl http://127.0.0.1:18090/api/portfolio/initialize/status
portfolio_ask times out"portfolio_ask and portfolio_refresh.MASSIVE_API_KEY)
for large portfolios; see "Which keys to obtain (by portfolio size)".docker compose down -v # removes the data volume — all envelopes lost
docker compose up -d # cold restart with auto-init
# Stop (preserves data)
docker compose down
# Stop and remove the data volume
docker compose down -v
InvestorClaw is a single-user, localhost-bound, deterministic-first analyzer. Several behaviors that automated scanners flag as "warning" are intentional design choices for this threat model. Documented here explicitly so reviewers can audit the trade-offs:
| Behavior | Why it's by design |
|---|---|
MCP + REST endpoints are unauthenticated on 127.0.0.1:18090 | Localhost binding (127.0.0.1: prefix on the port spec, never 0.0.0.0) is the security boundary. Any process running as the same user already has filesystem access to portfolios; adding token auth on a loopback API doesn't change that threat model. To expose the service to other hosts, put it behind your own auth layer (Tailscale, nginx + mTLS). |
Container auto-initializes on boot (IC_INITIALIZE_ON_BOOT=1) | The cold-cache cost on a 200+ position portfolio is 5–15 minutes; running setup → refresh → seed_ask at boot means agents see init_state: ready immediately on connect. Disable with IC_INITIALIZE_ON_BOOT=0 if you want manual control. The init does not exfiltrate data — it just primes the engine's read-only cache against the providers you've configured. |
API keys persist to /data/keys.env (mode 0600) | Keys need to outlive container restarts. The named volume is ic-engine-data; on the host that's a docker volume root-owned but only readable by the container's uid=1000(ic). To rotate or delete a key, use portfolio_keys_set / portfolio_keys_delete — both REST endpoints accept allowlisted names only, never logging values. |
| Portfolio summaries are sent to the configured narrative LLM provider | This is the value prop: the engine produces a deterministic envelope, the narrator turns it into prose. To keep narratives local, point INVESTORCLAW_NARRATIVE_ENDPOINT at a local Ollama / llama-server / vLLM endpoint (set INVESTORCLAW_NARRATIVE_PROVIDER=ollama). To run keyless without any narrator, omit TOGETHER_API_KEY — the engine returns a stub catalog summary instead. See PRIVACY.md for the full data-flow matrix. |
Image pulled from ghcr.io/argonautsystems/ic-engine | Pinned to a specific sha256 digest in compose.yml, not just the tag — guarantees reproducible builds even if the tag is later mutated. Verify the digest matches what your scanner expects before deploying. Container Apache 2.0 + bridge code MIT-0 in this repo; engine source at argonautsystems/ic-engine (pinned by SHA). |
For vulnerability disclosure see SECURITY.md. For the
privacy model (what stays local vs what goes to which provider) see
PRIVACY.md.
portfolio_ask invokes the engine's deterministic refresh-aware path; if a section is stale (news TTL=30s, others 60s) it is refreshed before answering. Earlier --no-refresh short-circuited routing entirely and produced a generic catalog blurb — that flag is intentionally NOT passed.50387b1 of mnemos-os/mnemos-ic-runtime.http://172.17.0.1:18090/mcp (Docker bridge IP) or via Compose service name http://ic-engine:8090/mcp (when both agent + ic-engine are in the same compose).portfolio_ask may take 5–15 minutes on a 200+ position portfolio when the envelope cache is empty (engine runs P0 holdings → P1 parallel performance/bonds/analyst/news → P2 synthesis → P3 optimize+cashflow → P4 peer, each consuming yfinance / FRED / Finnhub bandwidth). Subsequent calls hit the warm cache and return in seconds. Bridge subprocess timeout is 1800s for portfolio_ask and portfolio_refresh; engine P1 parallel-stage timeout is 600s.Section did not run on every other section): root cause was the engine's P1 parallel-stage timeout of 60s — performance/bonds/analyst/news running in parallel against yfinance overflowed it on large portfolios, asyncio.gather raised TimeoutError, the entire P1 result set was lost. Bumped to 600s.portfolio_ask: chain of five bugs — litellm stripped from the container; narrator wrapped the LLM call in a bare try/except; consultation client misrouted IP-addressed local servers; narrator pulled the short-context CONSULTATION_* model instead of the long-context NARRATIVE_* model; full envelope (200k+ tokens) overflowed even MiniMax-M2.7. All five fixed.--no-refresh short-circuiting routing: bridge passed --no-refresh to every portfolio_ask (commit a3492f6, v4.0.7), making the engine return the cached catalog blurb regardless of question. Reverted.mnemos-os/mnemos-ic-runtime)SKILL.md, compose.yml, install.yaml, agent-skills/**): MIT-0 (MIT No Attribution — LICENSE-MIT-0). Required for ClawHub plugin publishing; the no-attribution clause means downstream skill registries can re-host without preserving copyright notice.ghcr.io/argonautsystems/ic-engine:4.1.35-cpu@sha256:45a9c5bd2216ec41d6ce8bb89ae224158c668fbe652ee70c8264862c56aec3b4 (multi-arch amd64+arm64; also at :latest)RFC-v0.1.md in this bundle (mnemos-os/mnemos-ic-runtime GitHub repository)mnemos-os/mcp-contracts GitHub repositoryInvestorClaw is a portfolio analysis service. Educational use only — not investment advice.