diff --git a/README.md b/README.md
index 2f9ce91..ef505f0 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@ Configuration options:
- `nostrLock.dailyRoster` / `nostrLock.weeklyRoster` (explicit rosters)
- `dashboard.defaultCadenceView` (`daily`, `weekly`, or `all`)
- `dashboard.defaultStatusView` (`active` or `all`)
+- `dashboard.hashtag` (custom hashtag for lock events)
- `scheduler.firstPromptByCadence.daily` / `.weekly` (first prompt to run)
- `scheduler.paused.daily` / `.weekly` (array of agent names to pause locally)
@@ -88,6 +89,7 @@ npx --no-install torch-lock dashboard --port 4173
You can override configuration using environment variables:
- `NOSTR_LOCK_NAMESPACE`
+- `NOSTR_LOCK_HASHTAG`
- `NOSTR_LOCK_RELAYS` (comma-separated)
- `NOSTR_LOCK_TTL`
- `NOSTR_LOCK_QUERY_TIMEOUT_MS`
diff --git a/dashboard/index.html b/dashboard/index.html
index 6e28b08..cc640b9 100644
--- a/dashboard/index.html
+++ b/dashboard/index.html
@@ -37,7 +37,7 @@
>
lock events published by AI agents working on this TORCH project. Events
are kind 30078 filtered by the
- #torch-agent-lock hashtag on public
+ ... hashtag on public
Nostr relays.
diff --git a/src/docs/TORCH.md b/src/docs/TORCH.md
index 6a2a362..cdfe40e 100644
--- a/src/docs/TORCH.md
+++ b/src/docs/TORCH.md
@@ -44,6 +44,7 @@ node bin/torch-lock.mjs health --cadence daily
## Environment variables
- `NOSTR_LOCK_NAMESPACE`
+- `NOSTR_LOCK_HASHTAG`
- `NOSTR_LOCK_RELAYS`
- `NOSTR_LOCK_TTL`
- `NOSTR_LOCK_QUERY_TIMEOUT_MS`
@@ -77,6 +78,7 @@ Common settings:
- `nostrLock.dailyRoster` / `nostrLock.weeklyRoster` — optional per-project roster overrides.
- `dashboard.defaultCadenceView` — default dashboard view (`daily`, `weekly`, `all`).
- `dashboard.defaultStatusView` — default dashboard status filter (`active`, `all`).
+- `dashboard.hashtag` — custom hashtag for lock events (defaults to `-agent-lock`).
- `scheduler.firstPromptByCadence.daily` / `.weekly` — first-run scheduler starting agent.
- `scheduler.handoffCommandByCadence.daily` / `.weekly` — shell command run after lock acquisition; command must use `SCHEDULER_AGENT`, `SCHEDULER_CADENCE`, and `SCHEDULER_PROMPT_PATH` provided by `scripts/agent/run-scheduler-cycle.mjs`.
- `scheduler.paused.daily` / `.weekly` — array of agent names to exclude from scheduler rotation.
diff --git a/src/lib.mjs b/src/lib.mjs
index 9541615..eaf4b72 100644
--- a/src/lib.mjs
+++ b/src/lib.mjs
@@ -11,6 +11,7 @@ import {
getRelays as _getRelays,
getNamespace as _getNamespace,
getTtl as _getTtl,
+ getHashtag as _getHashtag,
} from './torch-config.mjs';
import {
DEFAULT_DASHBOARD_PORT,
@@ -191,6 +192,7 @@ export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
+ getHashtag = _getHashtag,
getTtl = _getTtl,
queryLocks = _queryLocks,
getRoster = _getRoster,
@@ -206,12 +208,14 @@ export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
const relays = getRelays();
const namespace = getNamespace();
+ const hashtag = getHashtag();
const dateStr = getDateStr();
const ttl = getTtl();
const now = nowUnix();
const expiresAt = now + ttl;
error(`Locking: namespace=${namespace}, agent=${agent}, cadence=${cadence}, date=${dateStr}`);
+ error(`Hashtag: #${hashtag}`);
error(`TTL: ${ttl}s, expires: ${new Date(expiresAt * 1000).toISOString()}`);
error(`Relays: ${relays.join(', ')}`);
@@ -259,7 +263,7 @@ export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
- ['t', `${namespace}-agent-lock`],
+ ['t', hashtag],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
['expiration', String(expiresAt)],
@@ -313,6 +317,7 @@ export async function cmdLock(agent, cadence, dryRun = false, deps = {}) {
log(`LOCK_EVENT_ID=${event.id}`);
log(`LOCK_PUBKEY=${pk}`);
log(`LOCK_AGENT=${agent}`);
+ log(`LOCK_HASHTAG=${hashtag}`);
log(`LOCK_CADENCE=${cadence}`);
log(`LOCK_DATE=${dateStr}`);
log(`LOCK_EXPIRES=${expiresAt}`);
@@ -419,6 +424,7 @@ export async function cmdComplete(agent, cadence, dryRun = false, deps = {}) {
const {
getRelays = _getRelays,
getNamespace = _getNamespace,
+ getHashtag = _getHashtag,
queryLocks = _queryLocks,
publishLock = _publishLock,
generateSecretKey = _generateSecretKey,
@@ -431,6 +437,7 @@ export async function cmdComplete(agent, cadence, dryRun = false, deps = {}) {
const relays = getRelays();
const namespace = getNamespace();
+ const hashtag = getHashtag();
const dateStr = getDateStr();
const now = nowUnix();
@@ -466,7 +473,7 @@ export async function cmdComplete(agent, cadence, dryRun = false, deps = {}) {
created_at: now,
tags: [
['d', `${namespace}-lock/${cadence}/${agent}/${dateStr}`],
- ['t', `${namespace}-agent-lock`],
+ ['t', hashtag],
['t', `${namespace}-lock-${cadence}`],
['t', `${namespace}-lock-${cadence}-${dateStr}`],
// No expiration tag -> permanent
diff --git a/src/torch-config.mjs b/src/torch-config.mjs
index cd8204e..b7ea2bc 100644
--- a/src/torch-config.mjs
+++ b/src/torch-config.mjs
@@ -282,3 +282,16 @@ export function getMinActiveRelayPool() {
assertPositiveCount(value, 'effective min active relay pool');
return value;
}
+
+export function getHashtag() {
+ const config = loadTorchConfig();
+ const envValue = process.env.NOSTR_LOCK_HASHTAG;
+ if (envValue) {
+ return envValue.trim();
+ }
+ if (config.dashboard.hashtag) {
+ return config.dashboard.hashtag;
+ }
+ const namespace = getNamespace();
+ return `${namespace}-agent-lock`;
+}