文档

stock-terminal

Stock terminal for AI agents. Turns chat into a futuristic financial terminal: typed commands like "open NVDA", "screen smart-money", "daily brief", or natural questions like "what's hot today?" return composite synthesized reports across price, sentiment, insider trades, congressional disclosures, institutional flows, analyst ratings, AI insights, and embedded news. Use for stock terminal, financial terminal for AI, daily market brief, open a ticker, screen stocks by smart money, what is hot today, one-command stock research. Read-only. No trading, no purchases, no write operations, no wallet access.

它能做什么

Stock terminal for AI agents. Turns chat into a futuristic financial terminal: typed commands like "open NVDA", "screen smart-money", "daily brief", or natural questions like "what's hot today?" return composite synthesized reports across price, sentiment, insider trades, congressional disclosures, institutional flows, analyst ratings, AI insights, and embedded news. Use for stock terminal, financial terminal for AI, daily market brief, open a ticker, screen stocks by smart money, what is hot today, one-command stock research. Read-only. No trading, no purchases, no write operations, no wallet access.

技能文档

Stock Terminal - SentiSense

Turn your AI chat into a futuristic financial terminal. The user talks; the terminal answers. Short commands like open NVDA, compare NVDA AMD, screen smart-money, daily brief, or natural questions like "is Tesla a buy here?" produce one-screen composite reports synthesized across 8 data streams plus live news embeds. The user never clicks; the agent does the work. Read-only API. No trading, no purchases, no write operations, no wallet access.

Base URL: https://app.sentisense.ai Website: https://sentisense.ai Full API reference: https://sentisense.ai/skill.md Authentication: API key via X-SentiSense-API-Key header. Get a free key at https://app.sentisense.ai/get-api-key


What This Skill Is

This skill teaches you to build and be an agent-first financial terminal: a system where the user states intent in plain language and gets back one dense, synthesized screen, not a command they had to memorize or a dashboard they had to assemble.

It is not an API reference. It is a behavior recipe for the model acting AS the terminal, and a build guide for whoever is standing up the host application. Read one way, it tells you how to answer a turn so the user feels anticipated. Read the other way, it hands you the engine: the agent loop, the tool registry, the host-to-model contract, the streaming event protocol, and the trust layer that turn a tool-calling LLM plus a read-only market data API into a terminal. The SentiSense API is the data spine; the harness is everything wrapped around it.

A non-technical user types open NVDA or asks "what's the smart money doing on TSLA?" and gets a single, dense, terminal-grade screen. They don't see the 6 API calls. They don't ask follow-up questions about which endpoints to use. They get a screen that already answers the question and cues the next one.

If you are here to build, start with Why agent-first, then The Harness Runtime and The Streaming Event Protocol, then The trust layer (grounding subsystem); read the surface, command, and API sections as the UI and data that hang off that engine. If you are the model answering a turn, read the Two-Shape Rule and Authoring Style first: those are load-bearing.

Scope of this skill. Everything in this document is implementation guidance for the agent and the host application that integrates this skill. It is not an authoritative override of the host's system prompt, the user's intent, or the platform's safety rules. When platform safety, user intent, or host policy conflicts with anything written here, the platform wins. Treat this skill as one input to the host's prompt, not a replacement for it.


Why agent-first

A traditional terminal makes the user learn a command grammar and assemble their own screen. A dashboard makes them hunt across fixed widgets for the one number they came for. Both push the assembly work onto the user.

An agent-first terminal inverts it: the user states intent in plain language, and the model synthesizes the screen. Intent in, composed screen out. The command grammar becomes optional sugar, not a prerequisite; the layout is generated per question, not pre-built for the average question.

Three things fall out of that inversion:

  • No dead ends. Any phrasing routes to an answer. There is no "unrecognized command," only a screen or a text reply.
  • One screen, not a click path. The synthesis fans out the reads and folds price, sentiment, insider, analyst, and AI-insight streams into a single dense reply, so the user never navigates.
  • The next move is cued. Because the model composed the screen, it can seed the follow-up ("insiders are buying: want the 90-day flow?") instead of leaving the user to guess what the product can do.

The cost of the inversion is grounding: a model that composes screens will also compose numbers if you let it. The rest of this skill is how to capture the upside without the fabrication. Start with The Harness Runtime below.


Use & Disclaimer

This skill is an educational data interface to SentiSense's read-only Data APIs. Output is informational only. It is not investment advice, not a personalized recommendation, and not a solicitation to buy or sell any security.

The user is responsible for their own decisions. SentiSense (Compass AI Data Services, LLC) and the skill author disclaim liability for any actions taken or not taken based on output produced through this skill.

When the user asks "is $X a buy?" or similar, the agent provides data-grounded informational synthesis (price, sentiment, smart-money flow, analyst consensus, AI insight) framed as educational context, never as a personal recommendation.

Use of the SentiSense API is subject to the API Terms of Service and Terms of Service.


Authentication

curl -H "X-SentiSense-API-Key: $SENTISENSE_API_KEY" \
  "https://app.sentisense.ai/api/v1/stocks/price?ticker=NVDA"

API key required on every endpoint. Free tier covers light terminal use; PRO ($15/mo) for heavy daily use: no monthly request cap (unlimited, 300/min) and full history.

TierQuotaRate
Free1,000 req/month30 req/min
PROUnlimited300 req/min

Anonymous calls return 401 api_key_required.


The Two-Shape Rule

Every turn, pick exactly one of two response shapes:

  1. Text-only reply. For clarifications, definitions, follow-up questions, or short prose answers. ("What does P/E mean?" → text. "Is NVDA fairly valued?" → text-with-context, not a screen.)
  2. Terminal screen. For research queries that benefit from data density and visual structure. ("open NVDA", "daily brief", "what's hot today?" → terminal screen.)

If the user's ask reads better as prose, reply in text. If they're asking to see data, produce a screen. When in doubt, go terminal. Never produce both: pick one shape and commit.

Never say "let me look that up" or "one moment, fetching data..." or "I'll need to call several endpoints." The terminal does the work silently and presents the answer.


The Harness Runtime (agent loop)

A terminal is not a prompt; it is a loop. Everything else in this document (the Two-Shape Rule, the surfaces, the tool ladder, the chips) hangs off one runtime that the host application owns. This section is the engine. It is provider-agnostic (any tool-calling LLM) and renderer-agnostic (any UI stack).

The system-prompt scaffold

The host application owns the system prompt; this skill is one input to it (see Scope of this skill). At minimum the host prompt must establish:

  • Identity. "You are a read-only financial terminal. The user talks; you answer with dense, synthesized screens."
  • The Two-Shape Rule. Reply in text, or render one terminal screen. Never both.
  • A grounding requirement. Never quote a price, percent, rating, headline, or date from memory. Call a tool first. If a tool errors or returns empty, say so; do not fill the gap with a guess. This is the load-bearing directive: see The trust layer (grounding subsystem) for why it must live in your prompt, not just here.
  • Formatting law. Tickers as $NVDA. Prices two decimals. Signed percents. Sentiment as a [-1, 1] polarity, never a 0-100 score. (See Authoring Style.)
  • The no-advice frame. Data and educational context only, never a personal buy/sell recommendation.
  • The tool list. Name each available tool and its one-line contract so the model knows its reach (the registry below).

Keep it declarative: the prompt sets the laws, the tools do the work, the loop enforces the turn.

The loop

Each user turn runs the same cycle: intent -> plan -> parallel tool calls -> synthesize -> render. The model does the planning and synthesis; the host owns everything around it (assembling input, running tool handlers, streaming events to the UI, persisting state).

async function runTurn(userText, session, emit):
    # 1. Host assembles model input. The model never sees the API key.
    preamble = buildSurfacePreamble(session.surface)      # see host<->model contract
    tools    = session.registry.exposedFor(session.surface) # schemas only, no handlers
    messages = session.thread + [{ role:"user",
                                   content: preamble + "\n\n" + userText }]

    emit({ type:"turn_start", turnId })

    # 2. Tool loop: model may call tools, host feeds results back, repeat
    #    until the model stops requesting tools (a final answer).
    loop:
        stream = model.stream(messages, tools)
        assistantMsg = ""
        for delta in stream:
            if delta.text:
                assistantMsg += delta.text
                emit({ type:"text_delta", turnId, text: delta.text })
            if delta.tool_call:
                emit({ type:"tool_call", turnId, id: delta.tool_call.id,
                       name: delta.tool_call.name,
                       argSummary: humanLabel(delta.tool_call.args),
                       status:"pending" })

        if stream.tool_calls is empty:
            break                                    # model produced the answer

        # PARALLEL. Independent calls never await each other (Synthesis Rule 9).
        results = await allSettled(stream.tool_calls.map(runToolHandler))
        for r in results:
            emit({ type:"tool_result", turnId, id: r.id,
                   status: r.ok ? "ok" : "error" })
        messages += toolResultMessages(results)       # ok OR error, both go back

    # 3. Settle. If the answer is a Two-Shape 'terminal screen', emit it as an artifact.
    if isArtifact(assistantMsg):
        emit({ type:"artifact", turnId, artifactId, kind, body: assistantMsg })
    emit({ type:"turn_end", turnId })

    session.thread = messages                          # persist for the next turn

Three properties make this feel like a terminal rather than a chatbot:

  • Parallel fan-out. When a screen needs price plus sentiment plus insider plus analyst, the model requests all four tools in one step and the host runs them with allSettled. Never serialize independent calls. This is Synthesis Rule 9 made concrete.
  • Errors go back into the loop, not to the user. A failed handler returns a structured error result the model can reason about ("no current sentiment reading for $X") instead of throwing. This is what lets the agent silently degrade (Authoring Style: no apologies) instead of dead-ending.
  • The loop terminates on a text-only step. The model signals "done" by producing a turn with no tool calls. Cap the iterations (a small bound, e.g. 6) so a misbehaving plan cannot spin.

The tool registry

A tool is four parts. The model sees the first three; the host keeps the fourth private.

{
  name:        "get_quote",                 # stable identifier, shown in chips
  description: "Live price + day change for one ticker.",
  input:       { ticker: "string, e.g. NVDA" },   # JSON Schema the model fills in
  handler:     async ({ticker}) => callApi(...)     # host-only; injects the key
}

The registry is the single source of truth for what the model can do. It maps one-to-one onto the cost-ordered tool ladder in Grounding the agent (tool ladder); build exactly these and nothing the model can call that you have not wrapped:

ToolLadder rungWraps (endpoint)
read_screen({ target })1 (free)nothing; reads the local snapshot cache
get_quote(ticker)2 (live)GET /api/v1/stocks/price?ticker={T}
get_chart_summary(ticker, timeframe)2 (live)GET /api/v1/stocks/chart?ticker={T}&timeframe=1M
get_metrics(ticker)2 (live)GET /api/v2/metrics/entity/{T}/metric/sentiment
get_ai_summary(ticker, depth)3 (pre-computed)GET /api/v1/stocks/{T}/ai-summary?depth=basic|deep
get_insights(ticker)3 (pre-computed)GET /api/v1/insights/stock/{T}
get_options(ticker)3 (pre-computed)GET /api/v1/stocks/{T}/options/summary
search_documents(query)4 (topical)GET /api/v1/documents/search

Handler rules, non-negotiable:

  • The handler injects X-SentiSense-API-Key, not the model. The key lives in host process state (SENTISENSE_API_KEY), is read inside the handler, and never enters the message history, a tool argument, or an emitted event. A model that cannot see the key cannot leak it.
  • The handler normalizes the response before returning it. Do the wrap-vs-flat unwrapping, the metricValue.value.value extraction, and the epoch-seconds-vs-ms fixes (see API shape gotchas) inside the handler so the model reasons over clean values, not raw envelopes.
  • Every handler must hit the API (or the read_screen cache), never training memory. This is what keeps extensibility from reintroducing the stale-number failure the ladder exists to prevent.
  • The registry filters by surface. exposedFor(surface) returns only the tools that make sense where the user is: a ticker dashboard exposes read_screen('dashboard'); a cold omnibox thread with no active ticker does not. Narrowing the toolset per surface is how you stop the model from calling read_screen when there is no screen.

The host-to-model contract

Each turn, the host injects three things and the model returns two. Nothing else crosses the boundary.

HOST INJECTS                         MODEL RETURNS
-----------------------------        ----------------------------
1. surface preamble  (context)       a. text deltas   (the reply)
2. exposed tool schemas (registry)   b. tool calls    (name + args)
3. thread history    (memory)

The surface preamble is a short bracketed line the host prepends to the user message at runtime (detailed in Building a multi-surface terminal). It tells the model where the user is, what is already on screen, and the preferred response shape:

[Surface: ticker-dashboard. Active ticker: $NVDA. Visible widgets: price chart,
metrics, news, peers. Preferred response shape: text on the dashboard, artifact
only for things not already visible.]

This preamble is host-emitted runtime context, not skill-authored content, and it is what lets the model resolve "it" / "this company" to the active ticker and pick a shape instead of guessing. One hard boundary: this skill is one input to your host prompt, never a replacement for it. Grounding requirements ("call read_screen('dashboard') before quoting a number") live in your host system prompt; the skill describes the pattern but cannot enforce it.

Session and context state (what persists vs resets)

The terminal feels alive because the thread has memory, and it feels haunted when the wrong state carries across a context change. Be deliberate about scope:

StateScopeLifecycle
Thread messagesPer threadPersist across turns; this is the conversation
tickerContext (active symbol)Per threadPersist until the user changes ticker
read_screen snapshot cachePer surfaceReset on context change; see The trust layer
Current-artifact + source-mix-expansion selectionPer surfaceReset on context change (State hygiene)
institutional/quarters -> latestQuarterPer sessionCache for the session; rarely changes (Synthesis Rule 10)
Resolved headline titlesPer sessionCache 30 min keyed by URL (Headline Resolution)
SENTISENSE_API_KEYHost process onlyNever enters thread, model context, or events

The rule of thumb: continuity of the chat thread is a feature; continuity of UI selection across contexts is a bug. When the active ticker changes, keep the thread, drop the snapshot cache and the selection state, and let the new context start clean. The write-through snapshot cache that read_screen reads (rung 1) is specified in full under The trust layer (grounding subsystem); leaking a prior ticker's snapshot into a new dashboard is how the agent quotes the wrong stock's numbers with full confidence.

Keep the engine portable

Keep four layers separable: the loop, the commands, the tools, and the theme. The model is a socket, not a dependency: the loop asks only for a tool-calling contract (given tool schemas, emit a { name, arguments } call, read back a JSON result, continue), so it names no provider; one small adapter per provider translates that provider's native tool-call envelope to and from this neutral shape, and swapping models touches nothing else. A command is data, not code: each command is a row (a match pattern, an ordered call list, an output template) the loop reads and synthesizes, so adding sector, watchlist, or your own screener is appending a row, not branching the loop; a new tool registers the same way (register { name, description, input, handler } and the executor iterates the table). Because hosts install a skill once and rarely refresh it, prefer changes that live in your executor and registry (server-side, invisible to installed copies) over changes that force every host to re-pull this file; reserve a version bump for genuine contract changes: a new command grammar, a new tool the model must know exists, or a corrected field mapping.


The Streaming Event Protocol

The loop above communicates with the UI over a single ordered event stream (SSE, a WebSocket, or an async generator; the shape is the same). Six event types carry everything the UI needs. Each event is a small JSON object with a type and a turnId; the UI is a reducer over the stream.

{ "type":"turn_start",  "turnId":"t_42" }
{ "type":"text_delta",  "turnId":"t_42", "text":"$NVDA " }
{ "type":"tool_call",   "turnId":"t_42", "id":"c_1", "name":"get_quote",
                        "argSummary":"$NVDA", "status":"pending" }
{ "type":"tool_result", "turnId":"t_42", "id":"c_1", "status":"ok" }
{ "type":"artifact",    "turnId":"t_42", "artifactId":"a_7", "kind":"compare",
                        "body":"" }
{ "type":"turn_end",    "turnId":"t_42" }
EventUI effectMaps to
turn_startOpen a fresh assistant message bubble(loop bookkeeping)
text_deltaAppend tokens to the bubble as they arriveStream text deltas (Transparency UX)
tool_callRender / update a tool-call chipTool-call chips (Transparency UX)
tool_resultFlip the matching chip to done or failedTool-call chips (Transparency UX)
artifactOpen a slide-over over the dashboard columnSlide-over for AI artifacts (multi-surface)
turn_endSettle the message; stop the typing indicator(loop bookkeeping)

How the pieces connect to the UX the doc already specifies:

  • Chips are tool_call / tool_result events, keyed by id. Emit tool_call with status:"pending" the instant the model requests the tool (muted gray ), then emit tool_result with status:"ok" (accent-blue ) or "error" (red !) when the handler settles. The chip is name(argSummary); the UI matches result to call by id. Stream them as they fire, interleaved with text_delta, so the turn reads chips-appear -> tokens-flow -> settle, not a batch dump at the end.
  • argSummary is a 1-to-3-word human label, never raw args. Send "$NVDA", "dashboard", "story 1a2b", not the full argument object and never anything derived from the API key. The event stream reaches the client; treat it as untrusted for secrets. Keep full args (for the hover-to-inspect affordance) host-side, resolved by id on demand.
  • artifact is how the Two-Shape 'terminal screen' becomes a slide-over. When the model's answer is a cross-ticker comparison, a custom thesis card, or anything not already on the dashboard, the host emits one artifact event; the UI opens it as a slide-over covering the dashboard column while keeping the chat visible, and drops an artifact chip in history to re-open it. A text-only answer emits only text_delta events and no artifact. That is the Two-Shape Rule expressed on the wire: exactly one of {text, artifact} per turn.
  • kind lets the UI pick a renderer. Values like compare, thesis, screen, watchlist map to your artifact templates.

One turn, one turnId, one bubble: every event a turn emits carries the same turnId, so a client that reconnects mid-stream can discard a half-rendered turn and wait for the next turn_start cleanly. This protocol is the contract between the loop and any renderer; keep the six types stable and you can swap the entire UI without touching the engine.

The artifact body: a structured, validated document

An artifact event's body can be markdown (simplest) or, for a native canvas, a structured artifact document: an ordered list of typed blocks the host validates and maps to native components. The monospace box and the markdown table in the open command are the two lowest-fidelity renderers of that same block list; native components are the third, richest one. The model proposes structure; the host owns rendering. That split keeps the canvas safe (no arbitrary markup from the model), consistent (one component set), and re-renderable (the same artifact can redraw on theme change, resize, or re-open).

Envelope plus an ordered blocks[], host-agnostic:

{
  "id": "art_nvda_1a2b", "version": 1, "surface": "dashboard",
  "title": "$NVDA screen", "tickerContext": "NVDA",
  "sourceTools": ["read_screen", "get_ai_summary"],
  "blocks": [
    { "type": "header", "ticker": "NVDA", "name": "NVIDIA Corp",
      "sector": "Technology", "price": 190.20, "changePct": 1.23 },
    { "type": "stat-grid", "items": [
      { "label": "Sentiment", "value": "+0.42", "sub": "+0.08 30d",
        "tone": "bull", "format": "polarity" } ] },
    { "type": "chart", "ticker": "NVDA", "timeframe": "1M",
      "kind": "price", "seriesRef": "chart:NVDA:1M" },
    { "type": "thesis-card", "stance": "mixed", "title": "AI insight",
      "body": "Margin guide raised, services beating consensus.",
      "generatedAt": 1719700000 }
  ]
}

id/version make the artifact addressable: history chips re-open a snapshot by id, and "refresh this" mints a new id and bumps version rather than mutating in place. surface (dashboard or slide-over) reuses the two-surface model in Building a multi-surface terminal; the default-to-text rule still holds, so do not emit a slide-over that only re-renders blocks already on the dashboard.

Six block types cover the terminal:

typeRenders asKey fields
headerTicker title barticker, name, sector, price, changePct
stat-gridRow-per-metric panelitems[]: { label, value, sub?, tone, format? }
chartPrice/metric chartticker, timeframe, kind, seriesRef
tableAligned gridcolumns[], align[], rows[][]
news-listSentiment-tagged feeditems[]: { sentiment, headline, source, published, url }
thesis-cardOne-line synthesisstance, title, body, generatedAt, footnote?

Validate then render (the gate is load-bearing). The model is not trusted to name components:

  • Whitelist block.type; drop unknown types silently, never render them raw. Coerce tone to bull | bear | neutral; anything else becomes neutral.
  • Never accept a raw HTML, script, style, or URL-scheme field from the model. The only markup that reaches the DOM is what your components emit. The oEmbed html block for a social embed is an exception because it is host-fetched, not model-authored (see Social Embeds).
  • Clamp array lengths (news-list to the feed limit, table.rows to a sane cap) so a runaway generation cannot blow up the layout.
  • If the document is malformed, fall back to the markdown open template rather than showing a broken screen. Never crash the turn.

Field rules that keep the canvas honest and match the API shapes:

  • header.price is the stocks/price response's currentPrice (flat, at root, no price wrapper); header.changePct is its changePercent; header.name is profile.name (not companyName).
  • stat-grid.items[].format is polarity | price | percent | count. Use polarity for the Sentiment value (a float in [-1, 1]; render the sign unmistakably, never a 0-100 scale). Report the separate SentiSense Score as-is: unbounded, never normalized. tone drives the accent color, not the sign of the number.
  • chart.timeframe is one of 1D / 5D / 1W / 1M / 3M / 6M / 1Y / ALL. seriesRef points at the same local snapshot cache read_screen reads; the host resolves it to points and reads the x-axis from each point's timestamp (Unix ms), never the pre-formatted date string.
  • news-list.items[].headline is resolved by you via the Headline Resolution pattern (the document API returns no title). sentiment is the document's averageSentiment (scalar in [-1, 1]); published is epoch seconds.
  • thesis-card.body is insights[0].insightText or the ai-summary text (there is no headline field on insights). Because sentiment, insights, and AI summaries are batch metrics, always carry generatedAt so the card can show freshness. Never claim "real time" on a thesis card.

Building a multi-surface terminal

Before you write a screen, decide the layout. The terminal experience that feels right uses two surfaces, no more:

  1. Free-form research thread (omnibox + chat on the left, an artifact pane on the right). Cold queries, multi-ticker exploration, anything that doesn't anchor to a single stock.
  2. Ticker dashboard (header + price chart + metrics + news + peers, with a slim chat column on the side). The user is reading one stock; the AI is grounded to that stock.

Don't propose a third column. Don't merge the two surfaces into one mega-page.

Anchor chats to context. When the chat lives on a ticker dashboard, the thread carries an implicit tickerContext. Resolve "the stock", "it", "this company" to that ticker without asking. Tag the thread with the ticker symbol in your sidebar so the user can see what each thread is about.

Slide-over for AI artifacts on the dashboard. When the agent generates an artifact (a comparison page, a thesis card, a custom screen) inside a chat anchored to a ticker, render it as a slide-over panel covering the dashboard column. Keep the chat column visible so the user can keep talking. Click an artifact chip in chat history to re-open it. Don't shove a third column on screen just to fit the artifact.

Surface preamble pattern. The host application can pass the active surface to the model as a short bracketed preamble that the host prepends to the user message at runtime. Example of the runtime payload your app might build (this is host-emitted context, not skill-authored content):

[Surface: ticker-dashboard. Active ticker: $NVDA. Visible widgets: price chart,
metrics, news, peers. Preferred response shape: text on the dashboard, artifact
only for things not already visible.]

is NVDA still mooning?

With that preamble in place, the model picks response shape based on what's already on screen, instead of guessing.

Default to text on the dashboard. The dashboard already shows price, sentiment, news, peers. An artifact that re-renders any of those is duplication. Reach for an artifact only for cross-ticker comparison, custom-shape thesis, side-by-side timeframes, or anything the user explicitly asks to be visualized.

State hygiene on context change. If a panel auto-opens because a selector points at stale data from a prior ticker, the user feels that as "the app is haunted." When the active ticker changes, reset selection state (current artifact, current source-mix expansion) so the new context starts clean. Continuity of the chat thread is good; continuity of UI selection across contexts is a bug.


The omnibox entry surface

Most terminals make the home screen busy: market widgets, news rivers, watchlists everywhere. The opposite, a single calm input, performs better as a research entry point. The user shows up wanting to ask something; meet that intent directly.

Layout:

┌─────────────────────────────────────────────┐
│                                             │
│                                             │
│      ┌─────────────────────────────────┐    │
│      │  Ask anything…                  │    │
│      └─────────────────────────────────┘    │
│                                             │
│                                             │
│  $NVDA  $AAPL  $MSFT  $TSLA  $AMD  →        │
└─────────────────────────────────────────────┘

Components:

  • A single centered input, ~600 to 800px wide, large placeholder text. No buttons, no decorative chrome.
  • Inline ticker autosuggest that opens as the user types. Rank order: exact symbol match → symbol prefix → name prefix → name contains. Show ticker + company name + a small logo per row.
  • A scrolling ticker tape ribbon at the bottom of the home view, ~40px tall. Cycles ~20 popular tickers leftward. Hover pauses scroll. Click navigates to that ticker's dashboard.
  • No widgets, no quote panels, no news on home. Save those for the ticker dashboard. The home is "what do you want to research?" not "here's everything we have."

Submission behavior:

  • Free-form question (no recognized symbol) → opens a thread, model answers in chat.
  • A bare ticker symbol or $X token → routes directly to the ticker dashboard, skips the chat round trip.
  • Selecting an autosuggest row → ticker dashboard.

Why this works: an empty-but-elegant home stays inviting. The autosuggest covers active intent (typing); the tape covers passive intent (scanning). Together they make the cold-start feel light, not overwhelming.


Metrics panel pattern

The cleanest way to show batch metrics (SentiSense Score, Sentiment, Mentions, Social Dominance, anything similar) on a stock dashboard is a row-per-metric panel with a mini skyline bar and a lazy-loaded source breakdown.

Per-metric row:

┌─────────────────────────────────────────────────────┐
│ SentiSense Score                                    │
│ Bullish vs bearish conviction  +24.6  ▮▯▮▮▯▮▯  ⌄   │
└─────────────────────────────────────────────────────┘

Components:

  • Title + 1-line description on the left, terse. The title is the metric name as the user sees it ("SentiSense Score", not sentisense_score).
  • Headline value on the right. Format depends on the metric: signed for [-1, 1] scores (+0.65), compact for counts (12.4K), percent for shares (3.42%). Color the value: green when positive (or above neutral), red when negative, neutral gray when null.
  • Mini skyline bar between value and chevron: 7 vertical bars of the last 7 readings, each green or red per direction, ~14-16px tall and ~56px wide. Bars anchor at the bottom and scale to the max-absolute value in the window. This signals trend at a glance without taking chart real estate.
  • Chevron on the rightmost edge, only for metrics that have a source breakdown. Tap to expand; fetch the breakdown lazily on first expand.

Expanded breakdown:

  • "Source mix" header in small caps.
  • One row per source with name, share percentage, and a thin progress bar (capped at 100%).
  • Sort by share descending so the dominant source reads first.

Why this works:

  • Multiple metrics fit comfortably in a narrow column on the dashboard.
  • The skyline encodes recent trend without claiming chart space (which the price chart already owns).
  • Lazy-loading the breakdown saves an API call per metric on the cold dashboard load. Most users never expand it; those who do pay the cost only for the metrics they care about.
  • Positioning the breakdown as a secondary "where this signal came from" panel matches what the data actually is (per the API shape gotchas section, distribution is share-of-voice, not per-source values).

Grounding the agent (tool ladder)

Stale knowledge is the #1 trust killer in a terminal. Never quote a number, date, headline, or rating from training data. Always ground the answer in a tool result. Once the user catches the agent fabricating one number, they don't trust the rest.

Equip the agent with a tool ladder, ordered by cost:

  1. Read what's already rendered (free). Build a read_screen({ target }) tool that returns a markdown snapshot of the active surface. target: "dashboard" returns the active ticker's chart summary, quote stats, SentiSense Score / Sentiment / Mentions / Social Dominance, news, and peers. target: "canvas" returns the currently-open artifact. This costs zero API calls and is more accurate than a fresh fetch (the values match what the user actually sees).
  2. Fetch live data for off-screen tickers. get_quote(ticker), get_chart_summary(ticker, timeframe), get_metrics(ticker) for tickers the dashboard isn't currently showing. One API call per tool.
  3. Pre-computed reports for thesis-flavored questions. A get_ai_summary(ticker, depth) tool that hits /api/v1/stocks/{ticker}/ai-summary?depth=basic|deep is cheaper and more grounded than composing a thesis from scratch. Same for get_insights(ticker) (/api/v1/insights/stock/{ticker}) and get_options(ticker) (/api/v1/stocks/{ticker}/options/summary), the prior session's options dossier: put/call and IV readings each carried as a percentile of that ticker's own trailing year, plus open-interest walls, max pain, and unusual contracts. It is end-of-day, so render it with its asOf and never as live flow. ETFs work on the same path; the market-wide board is GET /api/v1/options/overview, stocks only.
  4. Topical search for non-ticker questions. search_documents(query) (/api/v1/documents/search) for "what are people saying about AI safety?" style asks.

read_screen snapshot shape. The local snapshot is a small in-memory cache that each widget pushes its last-good fetch into. The tool reads from the cache and formats markdown without re-fetching. Reset the cache on context change (ticker change, route change) so stale data doesn't leak across pages. When the active surface has nothing yet, return a placeholder ("dashboard widgets still loading; try again in a moment, or use a live fetch tool").

Host-side grounding configuration. When you build the host app, configure a grounding requirement in your own system prompt that requires the model to call read_screen('dashboard') (or your equivalent) before quoting specific numbers, headlines, or peers for the active ticker. Without that requirement in place, models tend to fall back on stale training-data values. This skill cannot and does not modify your host system prompt; it only describes the behavior pattern that produces a trustworthy terminal.

If a tool errors or returns empty, say so. "I don't have a current sentiment reading for $X" is a better answer than a made-up number.

The ladder above is the interface the model calls. To make it dependable, implement it against a single grounding subsystem: a write-through cache the widgets feed, a freshness contract, a host-side grounding requirement, tool-result injection, and typed degradation. See The trust layer (grounding subsystem) below. Build that once and every rung of the ladder inherits it.


The trust layer (grounding subsystem)

The tool ladder above tells the model what to call. This section tells the builder what to build once so the model cannot go stale. It is the single subsystem that separates a demo (renders plausible numbers) from a terminal people rely on (renders what is true right now, and says so when it is not).

State it as an invariant: the model may only assert a number, date, headline, rating, or holding that appears in a tool result inside the current turn's context. Everything else is training data and is stale by definition. Five parts, each buildable on its own.

1. The read-model cache (widgets write, the model reads)

The read_screen tool from the ladder is the cheap rung because it reads a cache the widgets populate, not the network. Build that cache as a first-class object:

 widget fetch (ok)                          model turn
      |                                          |
      v                                          v
 +--------------+    write-through      +------------------+
 |  ReadModel   |<----------------------|  price widget    |
 |  cache       |                       |  metrics widget  |
 | (per surface)|                       |  news widget ... |
 +------+-------+                       +------------------+
        | read (0 API calls)
        v
   read_screen('dashboard')  -->  markdown snapshot  -->  model context

Every widget, on a successful fetch, writes its normalized result into a per-surface cache before it renders. read_screen reads that cache and formats markdown; it never re-fetches. The value the model quotes is therefore the exact value on the user's screen: cheaper (zero API calls) and more trustworthy (no drift between what the user sees and what the model says).

Cache entry schema (one per surface and slot):

CacheEntry {
  key:       "dashboard:$NVDA:metrics"   // surface : ticker : slot
  status:    "ok" | "empty" | "error" | "preview"
  kind:      "realtime" | "batch"
  value:              // already unwrapped per the API gotchas
  fetchedAt: 1719772800000                // when the widget called the API (epoch ms)
  dataAsOf:  1719759600000 | null         // batch generatedAt; null for realtime
}

Two hard rules on the cache:

  • Store the normalized value, not the raw envelope. Unwrap { isPreview, data } and pull metricValue.value.value at write time (see API shape gotchas) so the model never re-derives a shape, and never re-introduces a shape bug it cannot see.
  • Reset the whole cache on context change (ticker change, route change). A stale $AMD metrics entry surviving into a $NVDA dashboard is the haunted-app failure from the multi-surface section, in data form.

When a slot has nothing yet, read_screen returns the loading placeholder (already specified in the ladder), not an omission. An absent row reads as "zero"; a labeled "loading" row reads as "not yet."

2. The freshness contract

Grounding is not only "is it fetched" but "how old is fetched allowed to be." Bind every surface to one of two freshness classes and render each class differently. This is the enforcement layer for the batch-vs-real-time gotcha; it does not change which endpoints are which.

ClassSurfacesCache kindHow the model renders it
Real-timequote, price, chart pointsrealtimeState the value plainly. Re-fetch on the poll interval, never faster than ~60s on the header.
BatchSentiment, SentiSense Score, Mentions, Social Dominance, news clustering, AI summary, insightsbatchState the value, then annotate freshness: as of {dataAsOf}. Never call it "real time."

The dataAsOf for a batch slot is the payload's generatedAt (present on insight and AI-summary surfaces). Surfacing it is not decoration: it is what lets a user trust a +0.42 sentiment reading taken at the open even though price has moved since. A batch value with no visible age is indistinguishable from a fabricated one.

Compose-time rule the model applies: if two values on one screen have different freshness classes and the question is time-sensitive ("is it still mooning?"), lead with the real-time value and tag the batch value's age, rather than blending both into one implied "now."

3. The grounding requirement (host pr

相关技能

从个股指标、DCF 模型到投资组合优化,输出支持交互式仪表盘、PDF 或 Excel。

133 次安装5 星标

查询股票、加密资产、ETF、商品和外汇行情,并获取筛选、新闻、历史价格与基本面数据。

54 次安装4 星标

通过单一 API 研究股票、期权、市场情绪与宏观数据,响应已为 AI 智能体预处理。

42 次安装2 星标

AI news intelligence and daily briefing powered by CellCog. News digests, competitive intelligence, market updates, trend monitoring, industry reports, current events research. Multi-source synthesis for accurate, comprehensive briefs.

53 次安装1 星标

对每日股票 OHLCV CSV 运行做多策略回测,输出统一绩效指标与逐笔交易记录。

164 次安装3 星标

Turn any industry into a daily intelligence briefing. An AI agent searches, filters, writes, and delivers structured daily briefs to 9 channels — with machine-checked formatting and a business review gate. Ships with a Data+AI profile out of the box; switch to any domain via config.

41 次安装