description: "The file-backed credentials provider for users and maintainers choosing, configuring, or debugging the local credential store and its environment layering."
English | 中文
dsh-credentials-local keeps API keys and other secrets in a private file under your harness home. You can save credentials through the configuration UI or edit the file directly; changes reload automatically and saved values survive restarts. Credential lookup follows a fixed precedence: the launch environment wins, followed by the stored file, the project's .env, and the harness-home .env; a newly saved value immediately overrides older .env values. Only your OS user can read the file, but agent tool processes run as that same user, so this store cannot isolate secrets from the agent.
This package gives a composition a local credential store: save API keys and other secrets once, and every request that names them uses them. The common path is explicit: load the store, save keys through the configuration UI or ctx.credentials, and let the product resolve them when needed.
Use it as the default local store: the product's base composition loads it, and a key you save through a configuration UI takes effect immediately. Choose a different store when a deployment must keep provider keys away from its own agent — file permissions cannot do that, because the agent's tool processes run as your OS user (see "Who can read the file").
- name: '@deepseek-ai/dsh-credentials-local'
config:
path: /absolute/path/to/.credentials.yaml
| Field | Default | Meaning |
|---|---|---|
path |
<harness home>/.credentials.yaml |
Where the credential file lives |
dshHome |
$DSH_HOME or ~/.dsh |
Harness home used when path is omitted |
watch |
true |
Reload the file automatically when it changes on disk |
debounceMs |
100 |
Wait this long after a change before reloading, in milliseconds |
The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.
Save a key with set, remove it with unset, and check whether a key is configured with describe — the same operations the credential API provides:
import type { Context } from '@deepseek-ai/cordis'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
declare const ctx: Context
const ref = credentialRef('DEEPSEEK_API_KEY')
await ctx.credentials.set(ref, 'sk-…') // save
await ctx.credentials.describe(ref) // { configured, source?, writable } — never the value
await ctx.credentials.unset(ref) // remove
A key you save is usable by the next request that names it, and describe reports whether it is set, where it comes from, and whether you can write to it — never the value itself. Records persist in the same file, addressed by <owner>/<id> and managed with the seam's record operations (readRecord, describeRecord, listRecords, modifyRecord, deleteRecord).
Keys are resolved in one fixed order — the first place that has a value wins:
| Place | Writable? | Wins over |
|---|---|---|
The environment you launched in (DEEPSEEK_API_KEY=… dsh) |
no | everything |
| The stored file | yes (set/unset) |
both .env files |
Your project's .env (<invocation cwd>/.env) |
not here | your home .env |
Your home .env ($DSH_HOME/.env) |
not here | nothing |
The launching environment wins because a per-run override — DEEPSEEK_API_KEY=… dsh, a CI secret, a container -e — is this run's explicit intent; it cannot be edited from inside the product, so it is reported read-only and writes to it are refused. Everything else loses to the stored file, which is why a key you save takes effect immediately even when an older key sits in a .env; those two .env layers resolve when nothing is stored. The environment layer is the launcher's snapshot taken at launch (environment snapshot), so a variable exported after startup is not seen.
A versioned YAML document with one section per key space, and nothing else:
version: 1
refs:
DEEPSEEK_API_KEY: sk-…
OPENAI_API_KEY: sk-…
records:
llm-pi-ai/openai-codex:
kind: grant
payload: # written verbatim; this provider does not interpret it
type: oauth
access: eyJhbGciOi…
refresh: rft_9f8e7d…
expires: 1786000000000
llm-pi-ai/amazon-bedrock:
kind: api-key # environment values, no key: this route uses an AWS profile
env:
AWS_PROFILE: prod
llm-pi-ai/amazon-bedrock-dev:
kind: api-key # neither: the owner confirmed the ambient credential chain
You can edit the file directly — the store reloads it automatically and picks up the change, including a key or record you delete. refs holds key values by environment-variable name; records holds per-plugin credentials by <owner>/<id>, each tagged api-key or grant, whose grant payload the store keeps verbatim because only its owner can interpret it. Comments and the formatting of untouched entries survive product writes; a comment directly above an entry is that entry's note and is removed with it. The file holds only credentials, so anything else is refused loudly rather than silently ignored: a non-mapping root, an unknown top-level key, a key that is not addressable in its space, a wrong-typed or empty value, an unknown record tag or field, duplicate keys, and malformed YAML all fail at startup, and on a live reload the last good content keeps serving with a warning.
A key's value can be any text, multi-line values included — no quoting tricks needed. An empty value means "no key", which is why an empty string in the file is rejected: removing a key deletes it, it does not blank it. A grant payload must survive a JSON round trip, enforced on the way in and on the way out, so the store refuses a value it could not read back exactly as written. If the file on disk no longer parses, saving fails instead of overwriting content the product could not read.
Only your OS user can read the file: the product creates it with owner-only permissions, and on POSIX it refuses to load a file that any other user can read — the error tells you to run chmod 600. Windows has no mode to inspect, so the check is skipped there rather than faked. The agent is not another user: its tool processes run as you, so they can read the file like any other file you own. The product never hands the agent the file's path and never loads the file into the environment, so reaching a value takes a deliberate read of a path the agent was not given. That is discretion, not a boundary: a deployment that must keep provider keys away from its own agent cannot get there with file permissions.
DEEPSEEK_API_KEY=… dsh wins for this run; saving or removing it is refused. Clear the variable in the launching shell first.Read these pages when the provider-level contract is not enough. They move from the seam contract to the environment snapshot, the atomic-write primitive, and the boot-time environment layers.
resolve, describe, set, unset, the record operations, and the seam's update events.CredentialRef, per-operation resolution, UI-safe CredentialInfo, provider layers.process.env..env into the snapshot and process.env.Indirectly, through the consumers of ctx.credentials, which own any model-facing behavior a stored value enables.
No direct invalidation; stored values never enter a request prefix.
These limits define when the provider is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
describe; changing an environment-sourced credential takes a restart.dsh-atomic-write; the store re-reads on boot.