Falari Harness
Falari Data client for DeepSeek Harness (DSH): upload files to Falari storage and download them back, with an Agent Key as the only credential. The same six operations are exposed to the model as tools and to the user as a panel.
- Design and milestone log: docs/plan.md
- Test plan: docs/test-plan.md
- Release notes: CHANGELOG.md
The plugin wears the Falari mark: icon.png is the supplied 389×389 render,
used as delivered (no crop, no transparency), and the panel header embeds the
same image as its original WebP bytes. The sidebar draws crisp vector art so the
>_ survives at 20 px.
Status
| Phase | Scope | State |
|---|---|---|
| M0 | Bundle scaffold, install, panel/settings placeholders | done — installed in the desktop profile (2026-11-09) |
| M1 | Agent Key storage, falari_status tool, settings page | done (v0.2.0) |
| M2 | Quotes, upload queue, upload panel, falari_quote / falari_upload / falari_task | done (v0.3.0) |
| M3 | Intent list, download/decrypt, falari_list / falari_download | done (v0.4.0) |
| M4 | Editable settings, unit tests, test plan | done (v0.5.0); live acceptance pending a testnet key |
| M5 | Repository layout, changelog, license, manifest check | done (v0.5.0) |
Live-chain verification still needs a real testnet Agent Key: the settings page and every route/tool path are covered by the offline smoke tests, but nothing here has performed a paid upload against the testnet yet.
The full design is in docs/plan.md.
Project
- Developer: falari_labs
- Website: https://falari.xyz
- Package:
@falari/harness
Troubleshooting: DSH will not start
An earlier build wrote the sealed Agent Key as extra fields on an api-key
credential record. The credentials store rejects unknown fields and refuses to
boot:
credentials-local: record "falari-harness/agent-key" in ~/.dsh/.credentials.yaml
has unknown field "v"
0.8.20 stores the envelope in the record's key field instead and migrates old
records on startup. If DSH cannot start at all, repair the file offline:
node scripts/repair-credentials.mjs --file ~/.dsh/.credentials.yaml
The script rewrites only that record, backs the file up first, and prints no secret.
Install (development)
Open Plugins → Add in DeepSeek Harness and point it at this directory, or from an agent session call the plugin manager tool:
plugin_manager action=install_bundle target=/path/to/clients/harness
The bundle is installed as a local link (link:...). For a release build use
plugin_manager action=install_bundle target=@falari/[email protected]. Host code changes need the
plugin row to reload (toggle the bundle off/on, or restart DSH); client code
changes need a page refresh.
Storage duration can be a preset (permanent / 30d / 90d / 1y / 5y / 10y) or any
custom term in days, months or years. Fees follow the chain's RequiredFee:
whole days rounded up, minimum one day, and any term past 36 years is charged the
permanent price instead of growing forever.
Cancelling an upload opens a confirmation dialog with the refund breakdown (escrow, cancellation rate, withheld compensation, terminate service fee, amount returned) read from the chain before anything is signed.
Status reads are cached: the Host answers from <dataDir>/status-cache.json and
refreshes the chain in the background (a 15 s window), and the panel keeps its own
localStorage copy so the card renders before the first request returns.
The Agent Key is stored encrypted under a PIN, never in clear text: scrypt
derives the AES-256-GCM key from the PIN, the decrypted key lives only in memory,
and the panel locks it on demand or after lockIdleMinutes of inactivity
(default 60, presets or any value from 0 to 10080 minutes). A plugin row reload
keeps the unlock while the process lives. A
profile that already holds a plaintext key is flagged and re-sealed in place.
The Agent Key and the storage defaults are configured in the plugin itself, never in the global Settings dialog:
- Falari Data panel → Settings tab — always available, whatever the plugin page renders.
- Plugins → Falari Data — the bundle page card, or the row's Configure
control, driven by the
plugins.bundle.config/plugins.row.configslots. Both may take a browser refresh to appear after an install.
Configuration
The row accepts an optional config block (defaults shown). Put overrides in the
profile patch:
- id: falari-harness
name: '@falari/harness'
config:
network: testnet # testnet | mainnet (mainnet is not live yet)
defaultPrivacy: public # public | private
defaultDuration: permanent # permanent | 30d | 90d | 1y | 5y | 10y
maxFileSizeBytes: 2147483648
allowUploadOutsideWorkspace: false
allowRemoteRoutes: false # keep the panel routes on loopback only
# gatewayUrl / retrievalUrl / explorerUrl override the network presets
# timeoutMs defaults to 60000
# dataDir defaults to <DSH_PROFILE_DIR>/falari-harness
The Agent Key is not configuration: it lives in the DSH credential store as
the record falari-harness/agent-key.
Tools
| Tool | Purpose |
|---|---|
falari_status | Key state, tiers, allowance, balance, miners (refresh: true bypasses the cache) |
falari_quote | Price an upload (path or size_bytes) and check the budget |
falari_upload | Queue a workspace file; wait: true waits up to two minutes |
falari_task | Read one task or the recent list |
falari_list | This account's intents, newest first |
falari_download | Download one intent, verify roots, decrypt when private |
The plugin configuration page writes these values back through the profile's config editor, so saving restarts the plugin row.
Agent uploads are restricted to the Session working directory unless
allowUploadOutsideWorkspace is enabled. Files the user picks in the panel are
not subject to that guard.
Build
The Host half bundles the unpublished Falari SDK (@falari/agent) and the
precompiles package into lib/falari-sdk.cjs, so installing the bundle needs no
npm package for them:
npm run build:sdk
Run this after pulling a newer sdk/sdk-agent-ts.
Repository
The project lives in this directory and is published as the npm package
@falari/harness; the surrounding Falari checkout only hosts the SDK it builds
against and the docs it follows. Project home: https://falari.xyz.
The package name @falari/harness is the bundle identity: package.json name,
the Loader row in cordis.patch.yml and the id passed to
window.__ModuleLoader__.load must all stay equal, because the client-modules
scan matches the installed manifest name against the Loader specifier.
package.json exports ., ./client, ./cordis.patch.yml, ./package.json,
./locale/*.json and ./icon, and ships index.js, client.js, lib/**, the
patch, locales, icon, README, CHANGELOG and LICENSE. Everything else
(scripts/, docs/) is repository-only. npm pack --dry-run lists 18 files.
Publishing is a push of this directory to a new repository; nothing else in the manifest needs to change.
Layout
clients/harness/
├── .gitignore
├── CHANGELOG.md # release notes per milestone
├── LICENSE # MIT, © 2026 falari_labs
├── README.md
├── package.json # dsh.bundle + dsh.client manifest, exports, files
├── cordis.patch.yml # inserts the falari-harness row
├── icon.png
├── index.js # Host entry: wiring
├── client.js # Web client half: panel, tabs, settings
├── lib/
│ ├── config.js # row config resolution + edit validation
│ ├── errors.js # shared error shape and mapping
│ ├── routes.js # /api/falari-harness/* (same-origin, loopback)
│ ├── sdk.js # loads the bundled SDK
│ ├── service.js # FalariDataService: key, quote, budget, uploads, downloads
│ ├── tools.js # six falari_* Agent tools
│ └── falari-sdk.cjs # generated by npm run build:sdk (committed on purpose)
├── scripts/
│ ├── build-sdk.sh # esbuild bundle of @falari/agent + precompiles
│ ├── unit-host.mjs # pure-helper unit tests
│ ├── smoke-host.mjs # HTTP route smoke tests against a patched SDK
│ └── smoke-client.mjs # client.js render smoke test in a VM
├── locale/{en,zh}.json # plugin metadata
└── docs/
├── plan.md # v0.1 design and milestone log
└── test-plan.md # automated, live and panel checks
Test
npm test # unit + Host routes + client render, all offline
| Suite | What it does |
|---|---|
scripts/unit-host.mjs | config validation/defaults, name sanitizing, status and error mapping |
scripts/smoke-host.mjs | patches the SDK client and drives every HTTP route: status, key, quote, raw upload, task actions, intents, download, config save, origin and loopback guards |
scripts/smoke-client.mjs | loads client.js in a VM and server-renders the three slot components |
83 checks pass today. The manual and live testnet steps are in docs/test-plan.md.
Security
- The Agent Key is a bearer credential with upload rights. It is stored only in
the Host credential store; the browser never receives or persists the raw
far_...string, and no route returns it. - Upload routes accept only same-origin requests; cross-origin writes are rejected with 403.
- Routes answer loopback peers only unless
allowRemoteRoutesis enabled, so a listener bound to a wider address cannot be used to change the key or pay for uploads. - Browser uploads stream into a 0600 temp file under the plugin data directory and are deleted once the task completes or is cancelled; files older than 24 hours are swept at startup.
- The panel shows a private file's Data Key once the upload completes, because losing it makes the ciphertext unrecoverable.
- Downloads are written only inside the configured
downloadDir(default<dataDir>/downloads); anout_paththat escapes it is refused, and an existing file name gains a(1)suffix instead of being overwritten.