TerraFin / docs /agent /usage.md
sk851's picture
docs(agent): refactor for discoverability + fix post-split stale paths
3020135
---
title: Agent Usage
summary: How to use TerraFin's agent-facing surfaces from Python, CLI, HTTP, and the hosted assistant widget.
read_when:
- Using TerraFin as an agent tool
- Choosing between stateless and hosted agent surfaces
- Interpreting processing metadata
- Finding the right transport for a workflow
---
# Agent Usage
TerraFin is most useful to agents when it stays on the same path as the product.
That means:
- market and macro responses use the same progressive-history rules as the chart stack
- indicator math matches the chart indicators
- chart-opening uses the same session semantics as the browser UI
- every research response includes `processing`
If you are maintaining the runtime itself, read [hosted-runtime.md](./hosted-runtime.md)
and [architecture.md](./architecture.md). This document is the usage guide.
## If you want to do X, use this tool
Scan this table **first** before reaching for `requests` / `urllib` /
`yfinance` / pandas-html / regex parsers. Every row below is already shipped
with rate-limit, retry, cache, and progressive-history handling.
| If you want to... | Use this tool |
| ---------------------------------------------------------- | ------------------------------------------------------------------- |
| Resolve a ticker/name into a TerraFin route | `resolve(query)` |
| Get a chart-ready OHLC series | `market_data(name, depth, view)` |
| Get a one-asset snapshot (price action + indicators) | `market_snapshot(name, depth, view, force_refresh=False)` β€” response `asof` is the ISO date of the last bar served; pass `force_refresh=True` only for time-sensitive snapshots (mid-session, freshly-closed bar) to bypass the 24h `yfinance.full` cache |
| Get company profile / valuation fields | `company_info(ticker)` |
| Get earnings history (estimate / reported / surprise) | `earnings(ticker)` |
| Get income / balance / cashflow statement | `financials(ticker, statement, period)` |
| List a ticker's recent 10-K / 10-Q / 8-K filings | `sec_filings(ticker)` |
| Get a single filing's TOC (no full body) | `sec_filing_document(ticker, accession, primaryDocument, form)` |
| Pull one filing section's markdown body | `sec_filing_section(..., sectionSlug, form)` |
| Run DCF / reverse DCF / Graham / relative valuation | `valuation(ticker, ...)` (see SKILL.md for turnaround inputs) |
| Inspect FCF base candidates (3yr_avg / annual / TTM) | `fcf_history(ticker, years)` |
| Get S&P 500 index-level DCF | `sp500_dcf()` |
| Get 5y monthly beta vs mapped benchmark | `beta_estimate(ticker)` |
| Get statistical risk profile (tail risk, drawdown, regime) | `risk_profile(name)` |
| Run a fundamental quality / moat screen | `fundamental_screen(ticker)` |
| Get guru portfolio holdings (Buffett / Marks / Druck.) | `portfolio(guru)` |
| Get FRED-backed economic indicator series | `economic(indicators)` |
| Get macro summary + chart-ready series | `macro_focus(name, depth, view)` |
| Scan calendar events for a month | `calendar_events(year, month, categories, limit)` |
| Detect bubble (super-exp growth + log-periodic osc.) | `lppl_analysis(name, depth, view)` |
| Find chart-shape-similar historical episodes | `similarity_search(ticker, universe, period, top_n)` |
| Get market temperature signals | `fear_greed()`, `market_regime()`, `market_breadth()`, `trailing_forward_pe()` |
| Get top companies by market cap | `top_companies()` |
| Read the user's watchlist | `watchlist()` |
| Get chart-matching technical indicators | `indicators(name, indicators, depth, view)` |
| Get named pattern signals matching the latest bar | `patterns(name, depth, view)` |
| Read which panel/form the user is currently looking at | `current_view_context()` (hosted runtime only) |
| Open a chart artifact bound to the session | `open_chart(name)` (hosted runtime only) |
| List / authenticate / switch hosted models | `terrafin-agent models ...` (see [models.md](./models.md)) |
| Discover capability schemas programmatically | `GET /openapi.json` β€” filter `paths` to `/agent/api/*` |
Capability signatures with full parameter ranges and worked examples live in
[`skills/terrafin/SKILL.md`](https://github.com/KiUngSong/TerraFin/blob/main/skills/terrafin/SKILL.md).
The route summary further down this page is auto-generated and lists every
HTTP route.
## Don't roll your own
Reach for the TerraFin helper before hand-rolling any of the patterns below.
Each one has been a real source of breakage (rate-limit, cache miss,
parser inconsistency, hidden-API drift).
### SEC EDGAR β€” don't roll your own scrape
If you find yourself reaching for `urllib.request.urlopen("https://www.sec.gov/...")`,
regex-parsing the accession folder index, or stripping `<html>` tags by hand β€”
stop. The same content is one tool call away through the parsed + cached path:
| Manual approach (DON'T) | TerraFin tool (DO) |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `urlopen("https://www.sec.gov/Archives/edgar/data/<cik>/<acc>/")` to list exhibits | `sec_filing_document(ticker, accession, primaryDocument, form="8-K")` β†’ `toc` carries `exhibit-991-press-release` / `exhibit-992-supplemental-material` slugs |
| `urlopen("https://www.sec.gov/.../q1fy27pr.htm")` to grab the earnings PR | `sec_filing_section(..., sectionSlug="exhibit-991-press-release", form="8-K")` β†’ returns parsed markdown body |
| Strip `<p>` / `<table>` / `&nbsp;` with regex | already done β€” `sec_filing_section` returns clean markdown |
| Strip the `/ix?doc=` viewer wrapper off `documentUrl` | use `sec_filing_document` / `sec_filing_section` instead of dereferencing the URL yourself |
Why this matters:
- SEC rate-limits aggressively (~10 req/s per IP). The TerraFin helpers
share a single `SECClient` with rate-limit + retry; raw `urllib` does not.
- Parsed markdown is cached 30 days under `sec.parsed`; raw fetches re-pull
every newsletter run.
- Bypassing the cache risks SEC IP-banning the host.
The `form` arg is required for 8-K exhibit content β€” service defaults to
`"10-Q"`, and 8-Ks called without `form="8-K"` produce only the 4 KB cover
sheet stub (no exhibits appended). Pull `form` from
`sec_filings(...)["latestByForm"][...]["form"]` (or from any flat-list entry)
and pass it through every call.
### 8-K exhibits β€” don't bypass `fetch_and_parse_filing`
If you're tempted to call `download_filing(...)` then `parse_sec_filing(...)`
yourself for an 8-K, you'll get the 4 KB cover sheet with **no exhibits
appended**. The exhibit-appending logic lives only in
`fetch_and_parse_filing(cik, accession, doc, "8-K", include_images)`.
| Manual approach (DON'T) | TerraFin tool (DO) |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `parse_sec_filing(download_filing(cik, acc, doc), "8-K")` | `fetch_and_parse_filing(cik, acc, doc, "8-K", include_images=False)` β€” appends every `EX-99.x` exhibit |
| Manually loop `list_filing_files()` β†’ `download_exhibit()` β†’ `parse_sec_filing()` | `fetch_and_parse_filing(...)` β€” already does this with per-exhibit error markers |
| Call `sec_filing_section` without `form="8-K"` | always pass `form="8-K"` β€” service defaults to `"10-Q"` |
The agent-facing path (`sec_filing_section`) wraps `fetch_and_parse_filing`
under the cache; only fall through to `fetch_and_parse_filing` directly when
writing non-agent data-layer code (route handlers, batch ingestion).
### Model catalog β€” don't reach into `_PROVIDER_CATALOG`
The provider catalog is a public function, not a private dict.
| Manual approach (DON'T) | TerraFin tool (DO) |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `from TerraFin.agent.models.management import _PROVIDER_CATALOG` | `from TerraFin.agent.models.management import list_provider_catalog` |
| `_PROVIDER_CATALOG["openai"].featured_model_refs` | `get_provider_catalog("openai").featured_model_refs` |
| Iterate the private dict to discover providers | `for entry in list_provider_catalog(): ...` |
The private `_PROVIDER_CATALOG` mapping may be renamed or reshaped without
notice; `list_provider_catalog()` / `get_provider_catalog(provider_id)` are
the stable surface.
## Choose a surface
There are two agent-facing HTTP families:
- `GET/POST /agent/api/*`
Stateless capability access, best when another agent already owns the model loop
- `GET/POST /agent/api/runtime/*`
Stateful hosted runtime access, best when TerraFin should own the conversation and tool loop
In the browser, the hosted runtime is exposed through the floating **TerraFin Agent**
widget available on main interface pages such as `/dashboard`, `/chart`, and `/stock/<ticker>`.
That panel is always the public surface. There is no guru picker in the default
product flow.
If the deployment does not have a usable hosted model configured, the widget
stays in an info-only state:
- it shows the local setup warning
- it does not create a runtime session
- it does not accept chat input until the hosted model credentials are valid
Hosted runtime chat history is local transcript history:
- each session is stored as an append-only JSONL transcript
- history list and previews are derived from the transcript index
- deleting a session archives the transcript and removes it from active history
- raw tool JSON stays in the transcript for replay, but is hidden from user-facing previews
TerraFin Agent may also route some research questions through hidden investor
roles before replying. Today those roles include Warren Buffett, Howard Marks,
and Stanley Druckenmiller.
Important product rules:
- users still talk only to `TerraFin Agent`
- `TerraFin Agent` is the **orchestrator**; hidden persona roles are
reached by its own LLM via `consult_warren_buffett`,
`consult_howard_marks`, and `consult_stanley_druckenmiller` tool
calls (no pre-intercept router β€” see the architecture diagrams in
[architecture.md](./architecture.md#orchestrator-persona-subagents))
- the hidden guru path is research-only in v1
- hidden guru sessions are not shown in normal session history
- hidden guru roles cannot be created through the normal public runtime API
- hidden guru session ids are not valid public session/task/approval resources
- internal guru handoff uses a structured memo contract before the main assistant synthesizes a reply
## Disclosure
The hosted TerraFin Agent produces **research-oriented analysis, not personalized
investment advice**. A few properties follow from that framing and should be
called out to downstream product surfaces, integrators, and end users:
- the agent cannot place trades, hold funds, or take fiduciary responsibility
- outputs are grounded in tool results and what the user says in the same
session; the agent should not invent numbers it has not seen in a tool result
- persona consults (Buffett, Marks, Druckenmiller) are framed as research
voices, not registered advisor recommendations
- a `confidence` score β‰₯ 80 returned by a persona consult carries at least one
citation β€” the `GuruResearchMemo` validator clamps unsupported high-confidence
memos to 60 before returning them to the orchestrator (see
[guru/memo.py](https://github.com/KiUngSong/TerraFin/blob/main/src/TerraFin/agent/guru/memo.py))
The orchestrator's system prompt carries a matching `DISCLOSURE` paragraph so the
model stays inside this framing. Product surfaces that embed the widget or call
the runtime HTTP API are responsible for any **user-facing** disclosure copy
their jurisdiction requires β€” the agent itself does not render one.
## Choose a transport
| Transport | Use it when... |
|-----------|----------------|
| Python client | TerraFin is importable locally and low latency matters |
| HTTP API | TerraFin is already running as a service |
| CLI | Shell composition is the simplest fit |
| Skill artifact | Another agent environment needs portable instructions |
`TerraFinAgentClient(transport="auto")` follows the normal rule:
1. Python when no `base_url` is set
2. HTTP when a `base_url` is set
## Default request policy
For market and macro requests:
- start with `depth="auto"`
- inspect the returned `processing`
- rerun with `depth="full"` only when the task genuinely needs long-range context
For company info, earnings, financials, portfolio, and calendar data:
- the payload should already be complete
- `processing.isComplete` is expected to be `true`
Charts are optional. Open them when the chart itself materially helps the task.
## Processing metadata
Every structured research response includes a top-level `processing` object.
```json
{
"processing": {
"requestedDepth": "auto",
"resolvedDepth": "recent",
"loadedStart": "2023-04-04",
"loadedEnd": "2026-04-04",
"isComplete": false,
"hasOlder": true,
"sourceVersion": "yfinance-v2",
"view": "weekly"
}
}
```
| Field | Meaning |
|-------|---------|
| `requestedDepth` | What the caller asked for |
| `resolvedDepth` | What TerraFin actually returned |
| `loadedStart` / `loadedEnd` | Loaded time span for time-series tasks |
| `isComplete` | Whether older data still exists outside the response |
| `hasOlder` | Whether the request can be deepened |
| `sourceVersion` | Provider/cache version hint |
| `view` | Effective timeframe transform |
## Common tasks
Multi-step task recipes. For a one-row "what tool do I need" lookup, scan the
[tool-index table at the top](#if-you-want-to-do-x-use-this-tool) instead.
Rows marked **(helper)** are composed module-level functions exported from
`from TerraFin.agent import …` β€” not registered capabilities. They wrap
one or more capabilities (e.g. `compare_assets` calls `resolve` + `market_snapshot`
under the hood). They're not in the 30-row capability table because they
don't appear in the agent capability registry.
| Task | Recommended entrypoint |
|------|------------------------|
| Ticker brief | `resolve(...)` then `market_snapshot(...)` |
| Market snapshot | `market_snapshot(name, depth="auto", view="daily")` |
| Compare assets | `compare_assets([...], depth="auto", view="daily")` **(helper)** |
| Macro context | `macro_context(name, depth="auto", view="daily")` **(helper)** |
| Portfolio context | `portfolio_context(guru)` **(helper)** |
| Stock fundamentals | `stock_fundamentals(ticker, statement="income", period="annual")` **(helper)** |
| Stock DCF | `valuation(ticker, projection_years=..., fcf_base_source=..., breakeven_year=..., breakeven_cash_flow_per_share=..., post_breakeven_growth_pct=...)` |
| FCF history candidates | `fcf_history(ticker, years=10)` |
| SEC filings | `sec_filings(ticker)` β†’ `sec_filing_document(..., form=...)` β†’ `sec_filing_section(..., sectionSlug=..., form=...)`. **Pass the actual `form` string** ("10-K" / "10-Q" / "8-K" / "8-K/A") on every call β€” service defaults to "10-Q". For 8-K, the TOC includes `exhibit-991-press-release` / `exhibit-992-supplemental-material` slugs holding the earnings PR + CFO commentary bodies. |
| Sentiment / breadth | `fear_greed()`, `market_regime()`, `market_breadth()`, `trailing_forward_pe()` |
| Calendar scan | `calendar_events(year=..., month=..., categories=..., limit=...)` |
| Bubble analysis | `lppl_analysis(name, depth="auto", view="daily")` |
| Chart similarity search | `similarity_search(ticker, universe="sp500+nasdaq100+kospi200", period="1y", top_n=20)` |
| Open chart | `open_chart(...)` when the chart is explicitly useful |
> See [Don't roll your own β†’ SEC EDGAR](#sec-edgar--dont-roll-your-own-scrape)
> for the anti-pattern table and why the helper exists. The summary: pass
> `form="8-K"` explicitly (service defaults to `"10-Q"`), pull it from
> `sec_filings(...)["latestByForm"][...]["form"]`.
> The full per-capability call signature, parameter ranges, and worked
> examples (including DCF turnaround mode) live in
> [`skills/terrafin/SKILL.md`](https://github.com/KiUngSong/TerraFin/blob/main/skills/terrafin/SKILL.md) β€”
> the skill is the authoritative capability reference for both this doc and
> external agents.
## Minimal examples
### Python client
```python
from TerraFin.agent import TerraFinAgentClient
client = TerraFinAgentClient()
snapshot = client.market_snapshot("AAPL", depth="auto", view="daily")
```
### Hosted runtime helper
```python
from TerraFin.agent import create_runtime_session
session = create_runtime_session("terrafin-assistant")
session.send("Compare the S&P 500 and Nasdaq.")
session.display_notebook()
```
### CLI
```bash
terrafin-agent snapshot AAPL
terrafin-agent runtime-create-session terrafin-assistant
terrafin-agent models list --all
```
### HTTP
```bash
curl "http://127.0.0.1:8001/agent/api/market-snapshot?ticker=AAPL&depth=auto&view=daily"
curl -X POST "http://127.0.0.1:8001/agent/api/runtime/sessions" \
-H "Content-Type: application/json" \
-d '{"agentName":"terrafin-assistant"}'
```
### Browser UI
```text
http://127.0.0.1:8001/dashboard
Use the TerraFin Agent button in the lower-right corner.
```
## Route summary
Stateless capability routes (every hosted-runtime tool also has a parity HTTP
route under `/agent/api/*` β€” see `skills/terrafin/SKILL.md` for the full
30-capability table with Python / CLI signatures).
<!-- The route table below is auto-generated from
src/TerraFin/agent/runtime/capability.py by
`python scripts/generate-agent-artefacts.py`. Edit the registry, not
this section. Hand-edits will be overwritten. -->
<!-- generated:route-summary:begin -->
Data + chart:
- `GET /agent/api/calendar` β€” TerraFin calendar events for a month.
- `GET /agent/api/company` β€” Company profile and valuation fields for a ticker.
- `GET /agent/api/earnings` β€” Earnings history (estimate / reported / surprise) for a ticker.
- `GET /agent/api/economic` β€” Economic indicator series (FRED-backed).
- `GET /agent/api/financials` β€” Financial statement table (income / balance / cashflow) for a ticker.
- `GET /agent/api/indicators` β€” Chart-matching technical indicators for one asset.
- `GET /agent/api/lppl` β€” LPPL bubble analysis (super-exponential growth + log-periodic oscillation detection).
- `GET /agent/api/macro-focus` β€” Macro summary plus chart-ready series for one instrument.
- `GET /agent/api/market-data` β€” Chart-ready OHLC time series for one asset.
- `GET /agent/api/market-snapshot` β€” Compact market snapshot for one asset.
- `GET /agent/api/portfolio` β€” Guru portfolio holdings and summary metadata.
- `GET /agent/api/resolve` β€” Resolve a free-form query into a TerraFin route.
Valuation + fundamentals:
- `GET /agent/api/beta-estimate` β€” 5-year monthly beta with adjusted beta, RΒ², benchmark.
- `GET /agent/api/fundamental-screen` β€” Fundamental quality and moat screen for a ticker.
- `GET /agent/api/risk-profile` β€” Statistical risk profile (tail risk, convexity, vol regime, drawdown).
- `GET /agent/api/sp500-dcf` β€” Full S&P 500 DCF valuation (scenarios, sensitivity, methods).
- `GET /agent/api/valuation` β€” DCF (incl. turnaround mode), reverse DCF, relative valuation, Graham number.
SEC filings:
- `GET /agent/api/sec-filing-document` β€” Filing table-of-contents (sections + char counts) without full body.
- `GET /agent/api/sec-filing-section` β€” Verbatim markdown body of one filing section by slug.
- `GET /agent/api/sec-filings` β€” List recent 10-K / 10-Q / 8-K filings for a ticker with EDGAR URLs.
Sentiment / breadth / market state:
- `GET /agent/api/fear-greed` β€” CNN Fear & Greed index β€” score, rating, history.
- `GET /agent/api/market-breadth` β€” Standalone market-breadth metrics (% advancing, new highs, etc.).
- `GET /agent/api/market-regime` β€” Market regime classification with confidence and signals.
- `GET /agent/api/top-companies` β€” Top companies by market cap (private API or yfinance fallback).
- `GET /agent/api/trailing-forward-pe` β€” S&P 500 trailing vs forward P/E spread (history + summary).
- `GET /agent/api/watchlist` β€” The user's current watchlist (read-only).
Other:
- `GET /agent/api/fcf-history` β€” FCF history + 3yr-avg / latest-annual / TTM candidates.
- `GET /agent/api/patterns` β€” Named market patterns matching the latest bar for one asset.
- `GET /agent/api/similarity-search` β€” Chart-pattern similarity search across a stock universe.
<!-- generated:route-summary:end -->
> **View context is read-only.** The hosted runtime exposes a
> `current_view_context()` tool that returns the panel and form state the
> user is currently looking at β€” including the DCF input form's
> `projectionYears` / `fcfBaseSource` / `turnaroundMode` selections, the FCF
> history candidates already loaded, and the auto-selected DCF base source.
> The agent **cannot currently write back to the user's frontend form** (no
> `apply_dcf_inputs` / `set_form_state` tool exists). If the agent suggests
> input values, the user applies them manually. This gap is tracked.
Hosted runtime routes:
- `GET /agent/api/runtime/agents`
- `POST /agent/api/runtime/sessions`
- `GET /agent/api/runtime/sessions`
- `GET /agent/api/runtime/sessions/{session_id}`
- `DELETE /agent/api/runtime/sessions/{session_id}`
- `POST /agent/api/runtime/sessions/{session_id}/messages`
- `GET /agent/api/runtime/sessions/{session_id}/approvals`
- `GET /agent/api/runtime/tasks/{task_id}`
- `POST /agent/api/runtime/tasks/{task_id}/cancel`
- `GET /agent/api/runtime/approvals/{approval_id}`
- `POST /agent/api/runtime/approvals/{approval_id}/approve`
- `POST /agent/api/runtime/approvals/{approval_id}/deny`
OpenAPI is available at `/openapi.json`.
## Managing hosted models
The hosted runtime now has a small built-in model manager. Use it when you want
OpenAI, Gemini, or GitHub Copilot without hand-editing env vars every time.
This command family was inspired by OpenClaw's provider/model UX, but TerraFin
implements and persists it through its own runtime and CLI layers. See
[models.md](./models.md) for the explicit attribution and boundary.
```bash
terrafin-agent models list --all
terrafin-agent models current
terrafin-agent models use github-copilot/gpt-4o
terrafin-agent models auth login-github-copilot --set-default
```
`login-github-copilot` now runs a full GitHub device-login flow by default. For
non-interactive shells, pass `--token` instead.
For the full model-management guide, read [models.md](./models.md).
## Read next
- [hosted-runtime.md](./hosted-runtime.md)
- [models.md](./models.md)
- [architecture.md](./architecture.md)
- [../interface.md](../interface.md)
- [TerraFin skill on GitHub](https://github.com/KiUngSong/TerraFin/blob/main/skills/terrafin/SKILL.md)