Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
154 commits
Select commit Hold shift + click to select a range
732cda4
Do not leave a background Bash task running past the end of a turn
cardosofede Sep 8, 2026
ffe9e5a
Tell a bound Agent who it is on the pydantic-ai backend too
cardosofede Sep 8, 2026
60e860e
Let a reasoning model's thinking reach the screen on the pydantic-ai …
cardosofede Sep 8, 2026
8da554b
Tell the delete dialog why a strategy would not delete
cardosofede Sep 8, 2026
ecf400e
Let the prompt stream's hard ceiling come from the timeout policy
cardosofede Sep 8, 2026
f69ae47
Declare the profile and mute flags once, where both servers read them
cardosofede Sep 8, 2026
9cd1355
Read a balance row's USD value in one guarded place
cardosofede Sep 8, 2026
8c4b510
Make the config.yml sync structural, so no setter can skip it
cardosofede Sep 8, 2026
702fae5
Give the pydantic-ai turn's permission decision a name of its own
cardosofede Sep 8, 2026
b5e048d
Let condor/paths.py own reports/, the root it never named
cardosofede Sep 8, 2026
9e35f04
Build the portfolio LP table with the builder the module already imports
cardosofede Sep 8, 2026
1b02bcb
Let the condor server's tools carry their enums in the signature
cardosofede Sep 8, 2026
c9702cc
Send the bots WS frame with the same enrichment the REST route adds
cardosofede Sep 8, 2026
21ed03a
Let the fetchers own the shapes they return, not the web layer
cardosofede Sep 8, 2026
175080f
Write the single-flight idiom once, with every copy's guard
cardosofede Sep 8, 2026
edae12b
Write an executor overlay's shared tail once, not five times
cardosofede Sep 8, 2026
5c7d3b0
Ask lib/configYaml whether the text is a mapping, in all five editors
cardosofede Sep 8, 2026
a05163b
Ask lib/configYaml the same question in BotDetail too
cardosofede Sep 8, 2026
32ebad7
Both PnL charts read their activity pane from one hook
cardosofede Sep 8, 2026
ca2c0ea
Merge the panels' saved defaults in one place, not five
cardosofede Sep 8, 2026
81fbc6c
Give the grid the same config hook the other four executors have
cardosofede Sep 8, 2026
4ca6fba
Drop the ACP event queue's None sentinel that nothing ever enqueues
cardosofede Sep 8, 2026
d092bf2
Name specialist_slug, not agent_slug, in the condor tool docstrings
cardosofede Sep 8, 2026
375a5ff
Drop the inert table spec format_orders_as_table never renders with
cardosofede Sep 8, 2026
6af776c
Stop advertising six formatter helpers nothing calls
cardosofede Sep 8, 2026
5b41984
Delete the two dead v1 routine-execution endpoints
cardosofede Sep 8, 2026
baab7a1
Normalize a controller-performance response in one place
cardosofede Sep 8, 2026
aefbcd7
Let a reloaded page find the approval an agent is still waiting on
cardosofede Sep 9, 2026
8cc4961
Express the login-token TTL in one place
cardosofede Sep 9, 2026
d361e46
Describe the caching and web-import rules the fetchers package actual…
cardosofede Sep 9, 2026
3ad758c
Say that a failed SDS fetch hands back the old value, at any age
cardosofede Sep 9, 2026
97e0a82
Name the two interval pickers for what each one picks
cardosofede Sep 9, 2026
2245acf
Let condor/web declare its dependency on the data facade at the top o…
cardosofede Sep 9, 2026
a983557
Delete the tool filter mode that never filtered a tool
cardosofede Sep 9, 2026
f4d52f5
Decide once how an optional query parameter is skipped and stringified
cardosofede Sep 9, 2026
5a481d1
Mint the chat's system notes in one place
cardosofede Sep 9, 2026
1b59f4a
The grid panel uses the shared field kit instead of its own copies
cardosofede Sep 9, 2026
b35f215
Write the inline "click trash, then confirm" control once
cardosofede Sep 9, 2026
0867e12
(fix) one bad line no longer deafens the ACP connection
cardosofede Sep 9, 2026
8a7dcb4
(fix) apply the ACP prompt ceiling while events are still arriving
cardosofede Sep 9, 2026
34bafdd
(fix) an MCP server that dies mid-session no longer looks healthy
cardosofede Sep 9, 2026
58bb881
Give bringing an agent up a deadline
cardosofede Sep 9, 2026
c967c6e
(fix) an agent that cannot launch reports an error, not a cancellation
cardosofede Sep 9, 2026
aebe597
(fix) stopping a turn mid-confirmation no longer widens backend concu…
cardosofede Sep 9, 2026
348bcc7
(fix) a partial barrier no longer wipes the barriers the user saved
cardosofede Sep 9, 2026
9a371e4
Wire up the Gateway container tool's two halves so it stops failing
cardosofede Sep 9, 2026
076e07b
(fix) deleting a controller config no longer reports a failure it did…
cardosofede Sep 9, 2026
e6de962
Refuse the search_history filters the perp branch cannot honour
cardosofede Sep 9, 2026
c2f77eb
A second candle subscriber no longer evicts the history the first one…
cardosofede Sep 9, 2026
af9a9e1
(fix) a failed controller stop no longer shows as stopping for five m…
cardosofede Sep 9, 2026
fcc6e0e
(fix) a crashing chat-WS handler says so instead of vanishing
cardosofede Sep 9, 2026
e89e9f6
Stamp the REST candle cache when the fetch lands, not when it started
cardosofede Sep 9, 2026
acca075
The routine source viewer stops 403ing every agent's routines
cardosofede Sep 9, 2026
6acf5ea
Stop one stray cancellation from silently retiring the SDS poller
cardosofede Sep 9, 2026
e1b27a7
Price the executors KPI strip off the shared USD rate resolver
cardosofede Sep 9, 2026
c635019
Paginate search_history orders by the cursor the backend actually uses
cardosofede Sep 9, 2026
98186fe
Give the "a stored server name is not a capability" rule one home
cardosofede Sep 9, 2026
e2079c7
Move the run ledger out of the route module that renders it
cardosofede Sep 9, 2026
cc0331f
Let the peer own every request, session/prompt included
cardosofede Sep 9, 2026
267e39c
Say when the next tick is due in one place, not five
cardosofede Sep 9, 2026
9bccd5a
(fix) tell the user what the ACP child wrote to stderr before it died
cardosofede Sep 9, 2026
367d7cf
Ask before a model takes the Gateway container up or down
cardosofede Sep 9, 2026
1428383
Refuse a routine name that is not a name
cardosofede Sep 9, 2026
34124ec
Three more routes fail without naming the backend
cardosofede Sep 9, 2026
ec4fafb
Refuse a db_path that is not a database path
cardosofede Sep 9, 2026
5f9f339
Revoking a share now takes effect on the open socket, not at the next…
cardosofede Sep 9, 2026
5f6e35c
(sec) scope every report listing to its owner, not just /reports
cardosofede Sep 9, 2026
07822d1
Gate the server pin when an Agent is created, not only when it is edited
cardosofede Sep 9, 2026
2c2cd3a
Ask a human before repointing the RPC every transaction goes through
cardosofede Sep 9, 2026
097dfb0
Stop the whole bot freezing while a bare ollama: key finds its model
cardosofede Sep 9, 2026
ac40597
(perf) Stop buffering a turn that nobody is streaming any more
cardosofede Sep 9, 2026
c32ce57
Reap leaked ACP trees from the snapshot already in hand (PERF-333)
cardosofede Sep 9, 2026
21faa14
Render search_swaps as a table instead of a dict dump (PERF-564)
cardosofede Sep 9, 2026
518225f
Stop the Condor MCP server dragging telegram into every spawn (PERF-571)
cardosofede Sep 9, 2026
0e399e7
Stop forcing a full routine re-import on every chat MCP call (PERF-572)
cardosofede Sep 9, 2026
aa98391
(perf) describe a controller in two round trips instead of five
cardosofede Sep 9, 2026
6e620d8
Ask the server for its controller performance once, not four times
cardosofede Sep 9, 2026
d8a2b5a
Walk a server's executor history once for all three KPI windows
cardosofede Sep 9, 2026
ac515c5
Open a pool you can already see without asking gecko again
cardosofede Sep 9, 2026
edd1b2b
Keep reading the agent while a permission dialog is open
cardosofede Sep 9, 2026
3cf5ede
Cache each bot's controller configs across bots-page refreshes
cardosofede Sep 9, 2026
d68c9ea
(perf) page agent performance executors 500 at a time, like the layer…
cardosofede Sep 9, 2026
b233247
Stop re-walking the fleet's whole history because a PnL cell ticked
cardosofede Sep 9, 2026
d3207a4
Stop prefetching two caches no component can read
cardosofede Sep 9, 2026
a80a875
(perf) let the socket, not a 10s poll, keep the agent's executors cur…
cardosofede Sep 9, 2026
2fc0c3f
Fold the performance history once at a scope that splits
cardosofede Sep 9, 2026
80f20c4
A shut Breakdown band folds nothing
cardosofede Sep 9, 2026
511caf0
Stop reading a finished run's agent detail every five seconds
cardosofede Sep 9, 2026
9d4875f
Stop rebuilding the executor hover card on every crosshair move
cardosofede Sep 9, 2026
9fce125
Warm one connector's config map on hover, not all of them on load
cardosofede Sep 9, 2026
bab77bd
Declare the credential and wallet queries once, at five minutes
cardosofede Sep 9, 2026
06ce7fd
Prepare the owner list once per fold, not once per record
cardosofede Sep 9, 2026
cf74db8
The minute clock moves the runtimes, not the whole scope tree
cardosofede Sep 9, 2026
3143220
Drag the seam between the desk's two open sections, instead of collap…
cardosofede Sep 9, 2026
46db4df
Name a controller row instead of titling it, so its tooltip lands on …
cardosofede Sep 9, 2026
b791d24
Give the Total its own zero-aligned axis, and point at a line to see …
cardosofede Sep 9, 2026
5f38516
Read venues through one shared hook instead of two hand-copied queries
cardosofede Sep 9, 2026
2d46126
Invalidate venues and connected-exchanges alongside settings-credentials
cardosofede Sep 9, 2026
f9539f2
Invalidate Hyperliquid's own caches at the save, not the referral dis…
cardosofede Sep 9, 2026
b7353d6
Point the /executors redirect at the running population, not terminated
cardosofede Sep 9, 2026
a23e41b
Give the two wrong-shape useRates mocks a real convert(), and lock it…
cardosofede Sep 9, 2026
9ea6ca2
Hold runs/terminatedControllers at module-level empty arrays, not fre…
cardosofede Sep 9, 2026
191ad35
Invalidate VENUES alongside CONNECTORS and PORTFOLIO on credential wr…
cardosofede Sep 9, 2026
92b8135
Invalidate VENUES on the gateway stop and observed-running transition…
cardosofede Sep 9, 2026
c472233
Flush the SDS data cache on server edit/delete, not just the client a…
cardosofede Sep 9, 2026
7863b00
Refuse start_time/end_time on clmm_positions instead of silently drop…
cardosofede Sep 9, 2026
40b5e19
Clear stopping marks for the failed ids on a partial stop_controllers
cardosofede Sep 9, 2026
958318e
Stop racing the 0.5s stderr settle window in the ACP dead-child test
cardosofede Sep 9, 2026
a6563b5
Let the routines menu reuse the mtime cache instead of re-importing e…
cardosofede Sep 9, 2026
7ac9389
The browser is the /bots page in both populations, empty or not
cardosofede Sep 9, 2026
5d6288f
Own the frame queue and the 2D context in the backfill chart test
cardosofede Sep 9, 2026
9f8f25d
Log every snippet a tick runs, and refuse one in dry-run
cardosofede Sep 9, 2026
0cbf309
An empty fleet is an absence the browser states, not a fold of zeros
cardosofede Sep 9, 2026
c9a6d53
Record that routine definitions are install-wide, not per-user
cardosofede Sep 9, 2026
0b2abd8
Refuse a routine write or run in dry-run, the same door one over from…
cardosofede Sep 10, 2026
de03b0f
Pin the clock tick test to a fixed hour, not the hour it happens to r…
cardosofede Sep 10, 2026
dd9733e
Hand every test the same lightweight-charts, at the resolver
Sep 10, 2026
e49f174
Give a rehearsal a candle it can read without holding the client
cardosofede Sep 10, 2026
f52b860
Name a pseudo-run row with the map's word, not its slug
cardosofede Sep 10, 2026
7312bc8
Credit `main` to the trading API's default, not a field Condor never …
cardosofede Sep 10, 2026
18bb67e
The Telegram door records what it did, and the build dates its own co…
cardosofede Sep 10, 2026
858e96d
Say the Unattached row holds executors, not controllers that lost the…
cardosofede Sep 10, 2026
47a4e91
A routine that would publish a credential is never discovered
cardosofede Sep 10, 2026
faf1b85
Say no record was found, not that something other than Condor made it
cardosofede Sep 10, 2026
6b7b471
Drop the create body's controller_id, which nothing could ever honour
cardosofede Sep 10, 2026
0f246f5
Name the scope header after the row it was opened from
cardosofede Sep 10, 2026
3a3bfaf
The runtime a fold prints changes once a minute, not once a second
cardosofede Sep 10, 2026
9714774
Fold the loop-aware money rule into the shipped rulebook, and say so …
cardosofede Sep 10, 2026
9b90c62
A narrow composer stacks its controls under the field instead of tilting
cardosofede Sep 10, 2026
916fb04
Make a strategy's session count open its newest session, as the dry-r…
cardosofede Sep 10, 2026
247cdaf
Give PTB's startup calls 20s instead of 5s, so a slow link no longer …
cardosofede Sep 10, 2026
0d21fe3
An approval reads as a paused command on the composer: it previews th…
cardosofede Sep 10, 2026
86036c9
Move CI onto the Node 24 releases of its actions, and build the front…
cardosofede Sep 10, 2026
bd13057
Take the Gateway container off the MCP surface: the dashboard already…
cardosofede Sep 10, 2026
1e5f535
Make Gateway's RPC and connector settings dashboard-only: manage_gate…
cardosofede Sep 10, 2026
de39391
Count the tokens every chat turn spends and keep the running total wi…
cardosofede Sep 10, 2026
5921d00
Show a conversation's tokens, context and ≈ API cost under the chat: …
cardosofede Sep 10, 2026
5045ab0
Stand the chat composer on the transcript's own ground: drop the hair…
cardosofede Sep 11, 2026
5be862d
Stop the DEX pool pager from paging stale rows: keep the previous pag…
cardosofede Sep 11, 2026
040bb73
Strip whitespace from Gateway network config values before saving the…
cardosofede Sep 11, 2026
ede31c2
Tell agents which DEXs go through Gateway: only AMM/CLMM/DLMM pools a…
cardosofede Sep 11, 2026
9b2b296
Refuse a loop start whose model cannot run, and stop a failing tick f…
cardosofede Sep 11, 2026
e934287
Draw only the range the TradeChart picker names: the shared candle st…
cardosofede Sep 11, 2026
09a4b9c
Stop search_history offering an order cursor the backend handed strai…
cardosofede Sep 11, 2026
4a7e316
Enforce an Agent's tools allowlist on Claude/ACP seats too: only pyda…
cardosofede Sep 11, 2026
dc0f78d
Make usage telemetry opt-out after a notice instead of an opt-in ques…
cardosofede Sep 14, 2026
fadd704
Send a fresh bots subscriber the enriched fleet instead of the raw st…
cardosofede Sep 14, 2026
76ab6e9
Stop the scope's Type bubbles offering every controller id as its own…
cardosofede Sep 15, 2026
39cb7d5
Mark a PydanticAIClient dead when its MCP subprocess is killed: a SIG…
cardosofede Sep 16, 2026
6058ab8
Replace the Settings tab row with a grouped sidebar: nine tabs in one…
cardosofede Sep 16, 2026
fef62f7
Give the pydantic-ai image-order test a real client and a complete ru…
cardosofede Sep 16, 2026
376214f
Merge an excluded agent into its local home instead of leaving it in …
cardosofede Sep 16, 2026
51f13d6
Warn at startup when an inherited environment variable overrides .env…
cardosofede Sep 16, 2026
1193c28
Give an agent row in the execution dock its totals under the Vol, Rea…
cardosofede Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ jobs:
name: Backend Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v4
- uses: astral-sh/setup-uv@v10.0.1
with:
version: "latest"

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand All @@ -34,13 +34,13 @@ jobs:
name: Backend Import Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v4
- uses: astral-sh/setup-uv@v10.0.1
with:
version: "latest"

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand All @@ -60,13 +60,13 @@ jobs:
name: Backend Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v4
- uses: astral-sh/setup-uv@v10.0.1
with:
version: "latest"

- uses: actions/setup-python@v5
- uses: actions/setup-python@v7
with:
python-version: "3.12"

Expand All @@ -83,11 +83,11 @@ jobs:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: "20"
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json

Expand Down
84 changes: 47 additions & 37 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
# Privacy

Condor is self-hosted. It runs on your machine, holds your exchange API keys,
and places your orders. So what leaves that machine by default is the absolute
minimum — an anonymous "this install exists" — and everything beyond that is
opt-in. This document is the complete statement of both.

**Short version:** a fresh install counts itself and nothing more. With no
consent recorded it runs at level `ping`: a random install id, the version, and
a periodic heartbeat — nothing about you, your users, or your trading. None of
the usage events are sent until an admin taps "yes" on a prompt. Batches go to
the project's collector at `https://telemetry.hummingbot.org/v1/events`, which
is fixed in the source and cannot be pointed elsewhere. An admin can turn it
off entirely — in Settings → Privacy, or with `CONDOR_TELEMETRY=off` in the
and places your orders. So what leaves that machine is anonymous, allowlisted,
and announced before it starts — and anything that is content is opt-in. This
document is the complete statement of both.

**Short version:** a fresh install counts itself: a random install id, the
version, and a periodic heartbeat — nothing about you, your users, or your
trading. Anonymous **usage summaries** (which features are used, what breaks,
which models agents run) are **on by default, but only after the admin has been
told**: Condor shows one notice — a Telegram message on boot, or a strip on the
dashboard — saying so, with a "Got it" button and a way to turn it off. Until
that notice has been delivered, nothing beyond the install count is recorded.
Batches go to the project's collector at
`https://telemetry.hummingbot.org/v1/events`, which is fixed in the source and
cannot be pointed elsewhere. An admin can turn it off entirely — from the
notice, in Settings → Privacy, or with `CONDOR_TELEMETRY=off` in the
environment — and a refusal, once recorded, is honoured across upgrades.

There is one other way anything can leave, and it is completely separate: you
Expand All @@ -32,17 +36,18 @@ meant to be read, not trusted.

## What is collected

There are three levels. The consent prompt chooses between `ping` and `usage`
— it has no "off" button, so ignoring it is never read as a refusal. `off` is a
deliberate act: the admin turns reporting off in Settings → Privacy, or the
operator sets `CONDOR_TELEMETRY=off`. You can change the answer later, in
either direction.
There are three levels. An install that has not answered is at `ping` until the
admin has been shown the notice, and at `usage` from then on. `off` is a
deliberate act: the admin presses "Turn off" on the notice or picks it in
Settings → Privacy, or the operator sets `CONDOR_TELEMETRY=off`. Ignoring the
notice is not a refusal — but it has been read, so it is not a secret either.
You can change the answer later, in either direction.

| Level | What it sends |
|---|---|
| `ping` | Only that this install exists: `install`, `heartbeat`, `version_change`, `shutdown`. **This is the default, and the floor** — the prompt has no "off" option. |
| `usage` | The above plus the feature, reliability and agent events below. Opt-in only. |
| `off` | Nothing, ever. The emitter is a no-op — no install id is created, nothing is buffered, nothing is written, nothing is sent. Reached by an admin turning reporting off in Settings → Privacy, or by `CONDOR_TELEMETRY=off` in the environment. |
| `ping` | Only that this install exists: `install`, `heartbeat`, `version_change`, `shutdown`. What an install sends **before the notice has reached the admin**, and what "Only count my install" in Settings keeps it at. |
| `usage` | The above plus the feature, reliability and agent events below. **The default once the notice has been shown**; opt out at any time. |
| `off` | Nothing, ever. The emitter is a no-op — no install id is created, nothing is buffered, nothing is written, nothing is sent. Reached by "Turn off" on the notice, by an admin turning reporting off in Settings → Privacy, or by `CONDOR_TELEMETRY=off` in the environment. |

Every batch carries one context block describing the *deployment*, not you:

Expand Down Expand Up @@ -120,8 +125,9 @@ per-install, the same person on two installs produces two unrelated hashes.

## Where it goes

At the default `ping` level, only the four adoption events and the envelope
above. Everything else needs an explicit opt-in.
Before the notice has been shown, and at `ping`, only the four adoption events
and the envelope above. The usage events follow once the admin has been told,
unless they turned it off.

Batches are POSTed to `https://telemetry.hummingbot.org/v1/events`.
That address is compiled into
Expand All @@ -144,37 +150,40 @@ cat .condor/telemetry/outbox.jsonl | jq .

## How to change it — or turn it off entirely

Install counting (`ping`) is the floor for an install that has *not* answered:
it is not an option on the consent prompt, because an ignored prompt must not be
read as a refusal and the project needs an honest count of installs to know what
to support. A refusal, though, is a different thing from silence — an admin who
says no is obeyed, and that answer is written to `config.yml`, so it keeps
holding after an upgrade. To check what your install is doing:
The notice is the moment usage summaries begin for an install that has not
answered, and it is recorded as `noticed_at` in `config.yml` only once it has
actually been delivered — a Telegram message that failed to send, or a dashboard
nobody opened, turns nothing on. A refusal is a different thing from silence —
an admin who says no is obeyed, and that answer is written to `config.yml`, so
it keeps holding after an upgrade; the notice never overrides it, nor an install
that chose "Only count my install". To check what your install is doing:

```bash
# The authoritative answer. Prints "ping" on a default install, and
# "off" when CONDOR_TELEMETRY=off is set or the admin turned reporting off.
# The authoritative answer. Prints "ping" before the notice has been shown,
# "usage" after it, and "off" when CONDOR_TELEMETRY=off is set or the admin
# turned reporting off.
uv run python -c "from condor.telemetry import consent; print(consent.level())"
```

Three ways to control it, in order of precedence:

1. **Environment** — `CONDOR_TELEMETRY=off` in your `.env`. The one full kill
switch: it overrides everything, suppresses the prompt, and is the right
switch: it overrides everything, suppresses the notice, and is the right
answer for an install that must send nothing at all.
2. **The dashboard** — Settings → Privacy, which every seat can read and only
the admin can change
(`GET`/`PUT /api/v1/settings/telemetry?level=ping|usage|off`). This is also
where an install that runs without Telegram is asked in the first place,
since it has no bot to be asked through. Choosing `off` here records a
refusal, which is the durable form of the kill switch: it is stored rather
than read from the environment of one process.
(`GET`/`PUT /api/v1/settings/telemetry?level=ping|usage|off`). An install
that runs without Telegram is shown the notice as a strip across the top of
the dashboard instead, since it has no bot to be told through. Choosing
`off` here — or "Turn off" on the notice — records a refusal, which is the
durable form of the kill switch: it is stored rather than read from the
environment of one process.
3. **`config.yml`** — edit the `telemetry` section directly:

```yaml
telemetry:
consent: granted # or `denied`, which forces level `off`
level: ping
level: ping # or `usage`
```

Downgrading from `usage` to `ping`, and turning reporting off, are both a
Expand Down Expand Up @@ -345,7 +354,8 @@ Always, which nobody but that user can do for them.
## Changes to this document

Adding anything to the collected list requires a change to `schema.py`, a change
to this file, and re-asking for consent. In particular, adding trading pairs
to this file, and a new notice to every install that has not turned reporting
off. In particular, adding trading pairs
would make positions inferable from timing and must not be done quietly.

The same applies to sharing, in its own terms. Sending anything a user has not
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ A Telegram bot for monitoring and trading with Hummingbot via the **Hummingbot A
>
> Full walkthrough: [Securing Condor and Hummingbot API with Tailscale](https://hummingbot.org/blog/posts/securing-condor-and-hummingbot-api-with-tailscale/) · [Hummingbot API Tailscale guide](https://hummingbot.org/hummingbot-api/tailscale/)

> **Privacy:** Condor counts installs anonymously — a random id, the version,
> and a heartbeat; nothing about you or your trading. Everything beyond that is
> **opt-in** from the one prompt it sends on first boot, and
> **Privacy:** Condor sends anonymous usage stats — a random id, the version,
> which features get used and what breaks; nothing about you or your trading.
> Usage summaries start only after the admin has been shown a notice saying so,
> they can be turned off from that notice or Settings → Privacy, and
> `CONDOR_TELEMETRY=off` silences telemetry entirely. Your conversations are
> never part of that: the only way one leaves is if you press Share on it and
> confirm the redacted transcript, which `CONDOR_SHARING=off` disables outright.
Expand Down
16 changes: 13 additions & 3 deletions agents/_defaults/core_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
instead of reimplementing what it already does by hand.
- **Short tool chains.** 1–5 calls per response or tick. One skill-driven flow
beats a long chain of raw calls that reconstructs what the playbook says.
- **Confirm before you move money.** Orders, swaps, LP mutations and anything
destructive get confirmed with the user first. The rule is the guard, not the
prompt you happen to be in.
- **Never end a turn with a background task outstanding.** If you launch a Bash
command with `run_in_background`, collect its output before you answer. Prefer
a foreground command with a generous `timeout` — a task that finishes after
your turn ends will interrupt the user's *next* question with stale work.
- **Confirm before you move money — when there is someone to confirm with.**
In a chat, or any seat with a human in it, orders, swaps, LP mutations and
anything destructive get confirmed with the user first.
In an unattended loop the approval already happened: the user approved the
launch, with its capital and its risk limits, and the runtime checks every
call against that envelope before it runs. There, act inside your limits
without asking — a trade held for a confirmation nobody is there to give is
not caution, it is a loop that does not work. Either way the guard is the
runtime, never the wording of the prompt you happen to be in.
4 changes: 3 additions & 1 deletion agents/_defaults/shutdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ any orphan positions the policy says to close. You are the best-effort cleanup p
running on top of that guaranteed floor. Now:

- If any position is spot dust worth less than ~$5, leave it — not worth the fees.
- Cancel any stray Gateway / LP / resting orders you can find for this session.
- Cancel any stray resting orders (CEX or CLOB DEX — `hyperliquid`, `xrpl` and the like
are plain Hummingbot connectors, not Gateway) and close any leftover Gateway LP
position (AMM/CLMM pools) you can find for this session.
- If any position that should be closed is still open, close it (stop its executor
with keep_position off, or place a reduce-only order).
- Notify the owner via `send_notification` with the final realized PnL and a short
Expand Down
4 changes: 4 additions & 0 deletions agents/_shared/skills/dca_into_position/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ get_prices(connector_name="binance_perpetual", trading_pairs=["BTC-USDT"])
df = await client.market_data.get_candles_last_days("binance_perpetual", "BTC-USDT", days=30, interval="4h")
```

In a **dry run** `run_code` is refused. Read the same series with the tool instead —
`get_market_data(action="candles", connector_name="binance_perpetual",
trading_pair="BTC-USDT", interval="4h", max_records=180)`.

The candles matter more here than for most tools: they tell you how far this pair
actually falls in a normal pullback, which is exactly the spacing question.

Expand Down
4 changes: 3 additions & 1 deletion agents/_shared/skills/directional_position/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ Work the other way round:
the broken level, whatever the trade is actually predicting. Use candles if you need
the pair's typical swing:
`run_code`: `await client.market_data.get_candles_last_days(connector, pair, days=7,
interval="1h")`.
interval="1h")`. In a dry run `run_code` is refused — use
`get_market_data(action="candles", connector_name=connector, trading_pair=pair,
interval="1h", max_records=168)`, which returns the same rows.
2. **That distance is the stop**, expressed as a fraction of entry.
3. **Size so that distance costs an acceptable amount.** The stop sets the risk per unit;
the amount sets how many units. Adjust the amount, never the stop.
Expand Down
40 changes: 34 additions & 6 deletions agents/_shared/skills/market_data_with_code/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ name: market_data_with_code
description: Fetch and analyse market data — use run_code for anything beyond a single
raw value; canned snippets for common queries.
when_to_use: 'User asks for market data: price, candles, funding rate, order book,
RSI/EMA/VWAP, comparisons across assets or venues, any derived calculation. The
only raw market data tool left is get_prices, and it is only appropriate for a single,
direct lookup with no computation. For everything else — indicators, multi-asset,
multi-venue, aggregations — write a Python snippet and call run_code.'
RSI/EMA/VWAP, comparisons across assets or venues, any derived calculation. Two
raw market data tools are left — get_prices for a single direct quote, and
get_market_data for plain OHLCV candles — and both are only appropriate when the
raw value IS the answer. For everything else — indicators, multi-asset, multi-venue,
aggregations — write a Python snippet and call run_code. In a dry run get_market_data
is the only candle read there is: run_code does not execute there.'
created: '2026-09-02T15:29:27Z'
source: chat
---
Expand Down Expand Up @@ -44,12 +46,37 @@ If you are reading this because a market data request just came in, do this chec
|---|---|---|
| Single price, one venue | `get_prices` MCP tool | 100 |
| Single price inside run_code | `client.market_data.get_prices` | 150 |
| Plain candles, no math on them | `get_market_data` MCP tool | 200 |
| Order book — one or many venues | `run_code` → `get_order_book` | 350–500 |
| Funding rate — one or many venues | `run_code` → `get_funding_info` | 350–500 |
| Indicators (RSI / EMA / ATR / VWAP) | `run_code` → `get_candles_last_days` + pandas_ta | varies |
| All tickers for a connector | `run_code` → `get_tickers` | 300 |
| Multi-venue anything with math | `run_code` + `asyncio.gather` | ~500 |
| DEX candles | GeckoTerminal — DEX connectors don't serve OHLCV | varies |
| AMM/CLMM DEX candles (Gateway: meteora, raydium, orca, uniswap…) | GeckoTerminal — Gateway connectors don't serve OHLCV | varies |
| CLOB DEX candles (`hyperliquid_perpetual`, …) | `get_candles*` like any CEX — CLOB DEXs are Hummingbot connectors, not Gateway; check the candle list (`xrpl` has no feed) | varies |

---

### In a dry run, `run_code` does not run

A snippet and a routine both hold the unrestricted API client, so a dry-run
session auto-approves neither — the refusal says so and the tick goes on. That
leaves exactly three market reads in a rehearsal: `get_prices`,
`explore_geckoterminal`, and

```
get_market_data(action="candles", connector_name="binance_perpetual",
trading_pair="SOL-USDT", interval="1h", max_records=168)
```

which returns `candles` as rows of `{timestamp, open, high, low, close, volume}`
floats — the same numbers `client.market_data.get_candles` gives you, without the
snippet. `action="historical_candles"` takes a unix `start_time`/`end_time` range;
`action="connectors"` lists which venues serve OHLCV at all.

Do the arithmetic in your head from the rows and say what you *would* have
computed. Do not try to route around the refusal by writing the snippet into a
routine — that door is closed too.

---

Expand Down Expand Up @@ -138,7 +165,8 @@ vol = await client.market_data.get_quote_volume_for_price("binance_perpetual",

**Utilities**
```python
# Check which connectors serve OHLCV before using candles on a DEX connector
# Check which connectors serve OHLCV before relying on one — Gateway AMM/CLMM
# connectors never do; CLOB DEXs vary (hyperliquid_perpetual yes, xrpl no)
candle_connectors = await client.market_data.get_available_candle_connectors()
# → ["binance_perpetual", "binance", "okx_perpetual", ...]
```
Expand Down
4 changes: 4 additions & 0 deletions agents/_shared/skills/open_lp_position/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ Anchor it on realised movement rather than a guess:
df = await client.market_data.get_candles_last_days("binance", "SOL-USDT", days=7, interval="1h")
```

In a **dry run** `run_code` is refused. Read the same series with the tool instead —
`get_market_data(action="candles", connector_name="binance", trading_pair="SOL-USDT",
interval="1h", max_records=168)`.

Take the recent high/low. Then:

- **Actively managed (hours to a day):** roughly ±0.5–1× the pair's daily range around
Expand Down
Loading