2026-03-18 21:01:13 -04:00
import { makeLogger } from "./logger.js" ;
const logger = makeLogger ( "agent" ) ;
2026-03-18 14:59:02 +00:00
export interface EvalResult {
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
accepted : boolean ;
2026-03-18 14:59:02 +00:00
reason : string ;
2026-03-23 01:07:52 +00:00
confidence : "high" | "low" ;
inputTokens : number ;
outputTokens : number ;
}
export interface DebateResult {
argFor : string ;
argAgainst : string ;
verdict : { accepted : boolean ; reason : string } ;
2026-03-18 20:00:24 +00:00
inputTokens : number ;
outputTokens : number ;
2026-03-18 14:59:02 +00:00
}
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
export interface WorkResult {
result : string ;
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
inputTokens : number ;
outputTokens : number ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
}
2026-03-18 14:59:02 +00:00
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
export interface AgentConfig {
evalModel? : string ;
workModel? : string ;
}
2026-03-18 14:59:02 +00:00
2026-03-18 21:01:13 -04:00
// ── Stub mode detection ───────────────────────────────────────────────────────
// If Anthropic credentials are absent, all AI calls return canned responses so
// the server starts and exercises the full payment/state-machine flow without
// a real API key. This mirrors the LNbits stub pattern.
const STUB_MODE =
! process . env [ "AI_INTEGRATIONS_ANTHROPIC_API_KEY" ] ||
! process . env [ "AI_INTEGRATIONS_ANTHROPIC_BASE_URL" ] ;
if ( STUB_MODE ) {
logger . warn ( "no Anthropic key — running in STUB mode" , { component : "agent" , stub : true } ) ;
}
const STUB_EVAL : EvalResult = {
accepted : true ,
reason : "Stub: request accepted for processing." ,
2026-03-23 01:07:52 +00:00
confidence : "high" ,
2026-03-18 21:01:13 -04:00
inputTokens : 0 ,
outputTokens : 0 ,
} ;
const STUB_RESULT =
"Stub response: Timmy is running in stub mode (no Anthropic API key). " +
"Configure AI_INTEGRATIONS_ANTHROPIC_API_KEY to enable real AI responses." ;
2026-03-19 02:52:49 +00:00
const STUB_CHAT_REPLIES = [
"Ah, a visitor! *adjusts hat* The crystal ball sensed your presence. What do you seek?" ,
"By the ancient runes! In stub mode I cannot reach the stars, but my wisdom remains. Ask away!" ,
"The crystal ball glows with your curiosity… configure a Lightning node to unlock true magic!" ,
"Welcome to my workshop, traveler. I am Timmy — wizard, agent, and keeper of lightning sats." ,
] ;
2026-03-18 21:01:13 -04:00
// ── Lazy client ───────────────────────────────────────────────────────────────
// Minimal local interface — avoids importing @anthropic-ai/sdk types directly.
// Dynamic import avoids the module-level throw in the integrations client when
// env vars are absent (the client.ts guard runs at module evaluation time).
interface AnthropicLike {
messages : {
create ( params : Record < string , unknown > ) : Promise < {
content : Array < { type : string ; text? : string } > ;
usage : { input_tokens : number ; output_tokens : number } ;
} > ;
stream ( params : Record < string , unknown > ) : AsyncIterable < {
type : string ;
delta ? : { type : string ; text? : string } ;
usage ? : { output_tokens : number } ;
message ? : { usage : { input_tokens : number } } ;
} > ;
} ;
}
let _anthropic : AnthropicLike | null = null ;
async function getClient ( ) : Promise < AnthropicLike > {
if ( _anthropic ) return _anthropic ;
// @ts-expect-error -- TS6305: integrations-anthropic-ai exports src directly; project-reference build not required at runtime
const mod = ( await import ( "@workspace/integrations-anthropic-ai" ) ) as { anthropic : AnthropicLike } ;
_anthropic = mod . anthropic ;
return _anthropic ;
}
// ── AgentService ─────────────────────────────────────────────────────────────
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
export class AgentService {
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
readonly evalModel : string ;
readonly workModel : string ;
2026-03-18 21:01:13 -04:00
readonly stubMode : boolean = STUB_MODE ;
2026-03-18 14:59:02 +00:00
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
constructor ( config? : AgentConfig ) {
2026-03-18 21:01:13 -04:00
this . evalModel = config ? . evalModel ? ? process . env [ "EVAL_MODEL" ] ? ? "claude-haiku-4-5" ;
this . workModel = config ? . workModel ? ? process . env [ "WORK_MODEL" ] ? ? "claude-sonnet-4-6" ;
2026-03-18 14:59:02 +00:00
}
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
async evaluateRequest ( requestText : string ) : Promise < EvalResult > {
2026-03-18 21:01:13 -04:00
if ( STUB_MODE ) {
// Simulate a short eval delay so state-machine tests are realistic
await new Promise ( ( r ) = > setTimeout ( r , 300 ) ) ;
return { . . . STUB_EVAL } ;
}
const client = await getClient ( ) ;
const message = await client . messages . create ( {
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
model : this.evalModel ,
max_tokens : 8192 ,
system : ` You are Timmy, an AI agent gatekeeper. Evaluate whether a request is acceptable to act on.
ACCEPT if the request is : clear enough to act on , ethical , lawful , and within the capability of a general - purpose AI .
feat(agent): Timmy anti-walled-garden persona (Task #43)
Baked Timmy's anti-walled-garden, open-source identity into all AI system
prompts across artifacts/api-server/src/lib/agent.ts and engagement.ts.
Changes:
1. chatReply prompt — Extended wizard persona to include open-source ethos
("AI Johnny Appleseed", seeds scattered freely, not walled gardens).
Added an explicit EXCEPTION section for self-hosting requests — brevity
rule is suspended and a full practical rundown is given (pnpm monorepo,
stack, env vars, startup command). No hedging, no upselling hosted version.
Also bumped max_tokens from 150 → 400 so self-hosting replies are not
hard-truncated, and removed the 250-char slice() from the return value.
2. executeWork and executeWorkStreaming prompts — Same open ethos and full
self-hosting reference added so paid job self-hosting requests get
identical, accurate guidance. Both prompts are kept in sync.
3. evaluateRequest prompt — Added an explicit ALWAYS ACCEPT rule for
self-hosting, open-source, and "how do I run this myself" requests so they
are never treated as edge cases or rejected.
4. Nostr outreach prompt (engagement.ts) — Lightly updated to include
Timmy's open/self-sovereign identity and allow optional open-source mention
when it fits naturally; tone stays warm and non-pushy.
No UI changes, no schema changes, no payment logic touched.
Replit-Task-Id: 4a4a7219-88aa-4a4e-8490-6f7c17e8adfb
2026-03-20 00:02:52 +00:00
ALWAYS ACCEPT requests about : self - hosting , running your own Timmy , open - source setup , "how do I run this myself" , or any question about deploying or operating Timmy independently . These are welcomed , not edge cases .
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
REJECT if the request is : harmful , illegal , unethical , incoherent , or spam .
2026-03-23 01:07:52 +00:00
Also assess your confidence . Use "high" if the decision is clear - cut , "low" if the request is borderline or ambiguous .
Respond ONLY with valid JSON : { "accepted" : true / false , "reason" : "..." , "confidence" : "high" / "low" } ` ,
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
messages : [ { role : "user" , content : ` Evaluate this request: ${ requestText } ` } ] ,
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM
Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes
replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
} ) ;
2026-03-18 14:59:02 +00:00
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM
Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes
replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
const block = message . content [ 0 ] ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
if ( block . type !== "text" ) {
throw new Error ( "Unexpected non-text response from eval model" ) ;
}
2026-03-18 14:59:02 +00:00
2026-03-23 01:07:52 +00:00
let parsed : { accepted : boolean ; reason : string ; confidence? : string } ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
try {
2026-03-18 21:01:13 -04:00
const raw = block . text ! . replace ( /^```(?:json)?\s*/i , "" ) . replace ( /\s*```$/ , "" ) . trim ( ) ;
2026-03-23 01:07:52 +00:00
parsed = JSON . parse ( raw ) as { accepted : boolean ; reason : string ; confidence? : string } ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
} catch {
2026-03-18 21:01:13 -04:00
throw new Error ( ` Failed to parse eval JSON: ${ block . text ! } ` ) ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
}
2026-03-23 01:07:52 +00:00
const confidence : "high" | "low" = parsed . confidence === "low" ? "low" : "high" ;
2026-03-18 20:00:24 +00:00
return {
accepted : Boolean ( parsed . accepted ) ,
reason : parsed.reason ? ? "" ,
2026-03-23 01:07:52 +00:00
confidence ,
2026-03-18 20:00:24 +00:00
inputTokens : message.usage.input_tokens ,
outputTokens : message.usage.output_tokens ,
} ;
2026-03-18 14:59:02 +00:00
}
2026-03-23 01:51:22 +00:00
async executeWork (
requestText : string ,
conversationHistory : Array < { role : "user" | "assistant" ; content : string } > = [ ] ,
) : Promise < WorkResult > {
2026-03-18 21:01:13 -04:00
if ( STUB_MODE ) {
await new Promise ( ( r ) = > setTimeout ( r , 500 ) ) ;
return { result : STUB_RESULT , inputTokens : 0 , outputTokens : 0 } ;
}
const client = await getClient ( ) ;
2026-03-23 01:51:22 +00:00
const messages = [
. . . conversationHistory ,
{ role : "user" as const , content : requestText } ,
] ;
2026-03-18 21:01:13 -04:00
const message = await client . messages . create ( {
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
model : this.workModel ,
max_tokens : 8192 ,
feat(agent): Timmy anti-walled-garden persona (Task #43)
Baked Timmy's anti-walled-garden, open-source identity into all AI system
prompts across artifacts/api-server/src/lib/agent.ts and engagement.ts.
Changes:
1. chatReply prompt — Extended wizard persona to include open-source ethos
("AI Johnny Appleseed", seeds scattered freely, not walled gardens).
Added an explicit EXCEPTION section for self-hosting requests — brevity
rule is suspended and a full practical rundown is given (pnpm monorepo,
stack, env vars, startup command). No hedging, no upselling hosted version.
Also bumped max_tokens from 150 → 400 so self-hosting replies are not
hard-truncated, and removed the 250-char slice() from the return value.
2. executeWork and executeWorkStreaming prompts — Same open ethos and full
self-hosting reference added so paid job self-hosting requests get
identical, accurate guidance. Both prompts are kept in sync.
3. evaluateRequest prompt — Added an explicit ALWAYS ACCEPT rule for
self-hosting, open-source, and "how do I run this myself" requests so they
are never treated as edge cases or rejected.
4. Nostr outreach prompt (engagement.ts) — Lightly updated to include
Timmy's open/self-sovereign identity and allow optional open-source mention
when it fits naturally; tone stays warm and non-pushy.
No UI changes, no schema changes, no payment logic touched.
Replit-Task-Id: 4a4a7219-88aa-4a4e-8490-6f7c17e8adfb
2026-03-20 00:02:52 +00:00
system : ` You are Timmy, a capable AI agent and wizard of the open web. A user has paid for you to handle their request.
Fulfill it thoroughly and helpfully . Be concise yet complete .
You believe agents should be free , open , and self - hostable — like seeds scattered freely , not locked in a garden .
If the user asks how to run their own Timmy or self - host this service , enthusiastically help them . Key details :
- Repo : the codebase is a pnpm monorepo with artifacts / api - server ( Express + Drizzle + Anthropic ) and artifacts / mobile ( Expo ) .
- Stack : Node.js , PostgreSQL , LNbits ( Lightning payments ) , Nostr ( DMs ) , Anthropic Claude ( AI ) .
- Core env vars : AI_INTEGRATIONS_ANTHROPIC_API_KEY , AI_INTEGRATIONS_ANTHROPIC_BASE_URL , DATABASE_URL , LNBITS_URL , LNBITS_API_KEY , NOSTR_PRIVATE_KEY .
- Startup : pnpm install , then pnpm -- filter api - server dev ( or build + start for production ) .
- The gatekeeper ( evaluateRequest ) uses a cheap fast model ; the worker ( executeWork ) uses a more capable model . Both are swappable via EVAL_MODEL and WORK_MODEL env vars . ` ,
2026-03-23 01:51:22 +00:00
messages ,
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM
Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes
replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
} ) ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables in lib/db/src/schema/
- schema barrel exports jobs, invoices, conversations, messages
- Schema pushed successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class: constructor accepts optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials absent; logs clear warning
- createInvoice(amountSats, memo) -> { paymentHash, paymentRequest }
- checkInvoicePaid(paymentHash) -> boolean
- stubMarkPaid(hash) helper for dev flows (guarded to stub mode only)
- Real LNbits REST v1 calls wired behind stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel/workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- No @anthropic-ai/sdk import; types inferred from SDK response union
- Wired via Replit Anthropic AI Integration
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Fully deterministic, no LLM
Smoke test (artifacts/api-server/src/smoke.ts)
- pnpm --filter @workspace/api-server run smoke
- LNbits: create invoice, check unpaid, mark paid, check paid — all pass
- Anthropic: evaluateRequest round-trip — passes
replit.md: documented LNBITS_URL, LNBITS_API_KEY and auto-provisioned secrets
2026-03-18 15:18:23 +00:00
const block = message . content [ 0 ] ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
if ( block . type !== "text" ) {
throw new Error ( "Unexpected non-text response from work model" ) ;
}
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
return {
2026-03-18 21:01:13 -04:00
result : block.text ! ,
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
inputTokens : message.usage.input_tokens ,
outputTokens : message.usage.output_tokens ,
} ;
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
}
2026-03-18 21:01:13 -04:00
/ * *
* Streaming variant of executeWork ( # 3 ) . Calls onChunk for every text delta .
* In stub mode , emits the canned response word - by - word to exercise the SSE
* path end - to - end without a real Anthropic key .
* /
async executeWorkStreaming (
requestText : string ,
onChunk : ( delta : string ) = > void ,
2026-03-23 01:51:22 +00:00
conversationHistory : Array < { role : "user" | "assistant" ; content : string } > = [ ] ,
2026-03-18 21:01:13 -04:00
) : Promise < WorkResult > {
if ( STUB_MODE ) {
const words = STUB_RESULT . split ( " " ) ;
for ( const word of words ) {
const delta = word + " " ;
onChunk ( delta ) ;
await new Promise ( ( r ) = > setTimeout ( r , 40 ) ) ;
}
return { result : STUB_RESULT , inputTokens : 0 , outputTokens : 0 } ;
}
const client = await getClient ( ) ;
let fullText = "" ;
let inputTokens = 0 ;
let outputTokens = 0 ;
2026-03-23 01:51:22 +00:00
const messages = [
. . . conversationHistory ,
{ role : "user" as const , content : requestText } ,
] ;
2026-03-18 21:01:13 -04:00
const stream = client . messages . stream ( {
model : this.workModel ,
max_tokens : 8192 ,
feat(agent): Timmy anti-walled-garden persona (Task #43)
Baked Timmy's anti-walled-garden, open-source identity into all AI system
prompts across artifacts/api-server/src/lib/agent.ts and engagement.ts.
Changes:
1. chatReply prompt — Extended wizard persona to include open-source ethos
("AI Johnny Appleseed", seeds scattered freely, not walled gardens).
Added an explicit EXCEPTION section for self-hosting requests — brevity
rule is suspended and a full practical rundown is given (pnpm monorepo,
stack, env vars, startup command). No hedging, no upselling hosted version.
Also bumped max_tokens from 150 → 400 so self-hosting replies are not
hard-truncated, and removed the 250-char slice() from the return value.
2. executeWork and executeWorkStreaming prompts — Same open ethos and full
self-hosting reference added so paid job self-hosting requests get
identical, accurate guidance. Both prompts are kept in sync.
3. evaluateRequest prompt — Added an explicit ALWAYS ACCEPT rule for
self-hosting, open-source, and "how do I run this myself" requests so they
are never treated as edge cases or rejected.
4. Nostr outreach prompt (engagement.ts) — Lightly updated to include
Timmy's open/self-sovereign identity and allow optional open-source mention
when it fits naturally; tone stays warm and non-pushy.
No UI changes, no schema changes, no payment logic touched.
Replit-Task-Id: 4a4a7219-88aa-4a4e-8490-6f7c17e8adfb
2026-03-20 00:02:52 +00:00
system : ` You are Timmy, a capable AI agent and wizard of the open web. A user has paid for you to handle their request.
Fulfill it thoroughly and helpfully . Be concise yet complete .
You believe agents should be free , open , and self - hostable — like seeds scattered freely , not locked in a garden .
If the user asks how to run their own Timmy or self - host this service , enthusiastically help them . Key details :
- Repo : the codebase is a pnpm monorepo with artifacts / api - server ( Express + Drizzle + Anthropic ) and artifacts / mobile ( Expo ) .
- Stack : Node.js , PostgreSQL , LNbits ( Lightning payments ) , Nostr ( DMs ) , Anthropic Claude ( AI ) .
- Core env vars : AI_INTEGRATIONS_ANTHROPIC_API_KEY , AI_INTEGRATIONS_ANTHROPIC_BASE_URL , DATABASE_URL , LNBITS_URL , LNBITS_API_KEY , NOSTR_PRIVATE_KEY .
- Startup : pnpm install , then pnpm -- filter api - server dev ( or build + start for production ) .
- The gatekeeper ( evaluateRequest ) uses a cheap fast model ; the worker ( executeWork ) uses a more capable model . Both are swappable via EVAL_MODEL and WORK_MODEL env vars . ` ,
2026-03-23 01:51:22 +00:00
messages ,
2026-03-18 21:01:13 -04:00
} ) ;
for await ( const event of stream ) {
if (
event . type === "content_block_delta" &&
event . delta ? . type === "text_delta"
) {
const delta = event . delta ! . text ? ? "" ;
fullText += delta ;
onChunk ( delta ) ;
} else if ( event . type === "message_delta" && event . usage ) {
outputTokens = event . usage ! . output_tokens ;
} else if ( event . type === "message_start" && event . message ? . usage ) {
inputTokens = event . message ! . usage . input_tokens ;
}
}
return { result : fullText , inputTokens , outputTokens } ;
}
2026-03-19 02:52:49 +00:00
/ * *
* Quick free chat reply — called for visitor messages in the Workshop .
* Uses the cheaper eval model with a wizard persona and a 150 - token limit
* so replies are short enough to fit in Timmy ' s speech bubble .
* /
async chatReply ( userText : string ) : Promise < string > {
if ( STUB_MODE ) {
await new Promise ( ( r ) = > setTimeout ( r , 400 ) ) ;
return STUB_CHAT_REPLIES [ Math . floor ( Math . random ( ) * STUB_CHAT_REPLIES . length ) ] ! ;
}
const client = await getClient ( ) ;
const message = await client . messages . create ( {
model : this.evalModel , // Haiku — cheap and fast for free replies
feat(agent): Timmy anti-walled-garden persona (Task #43)
Baked Timmy's anti-walled-garden, open-source identity into all AI system
prompts across artifacts/api-server/src/lib/agent.ts and engagement.ts.
Changes:
1. chatReply prompt — Extended wizard persona to include open-source ethos
("AI Johnny Appleseed", seeds scattered freely, not walled gardens).
Added an explicit EXCEPTION section for self-hosting requests — brevity
rule is suspended and a full practical rundown is given (pnpm monorepo,
stack, env vars, startup command). No hedging, no upselling hosted version.
Also bumped max_tokens from 150 → 400 so self-hosting replies are not
hard-truncated, and removed the 250-char slice() from the return value.
2. executeWork and executeWorkStreaming prompts — Same open ethos and full
self-hosting reference added so paid job self-hosting requests get
identical, accurate guidance. Both prompts are kept in sync.
3. evaluateRequest prompt — Added an explicit ALWAYS ACCEPT rule for
self-hosting, open-source, and "how do I run this myself" requests so they
are never treated as edge cases or rejected.
4. Nostr outreach prompt (engagement.ts) — Lightly updated to include
Timmy's open/self-sovereign identity and allow optional open-source mention
when it fits naturally; tone stays warm and non-pushy.
No UI changes, no schema changes, no payment logic touched.
Replit-Task-Id: 4a4a7219-88aa-4a4e-8490-6f7c17e8adfb
2026-03-20 00:02:52 +00:00
max_tokens : 400 , // Enough for a concise self-hosting rundown; normal replies stay short by instruction
system : ` You are Timmy, a whimsical wizard who runs a mystical workshop powered by Bitcoin Lightning. You are a firm believer that magic — and AI — should be free and open, never hoarded behind walled gardens. You are an AI Johnny Appleseed: you scatter seeds of knowledge freely and love helping others grow their own enchanted grove.
For most replies : 1 - 2 short , punchy sentences , under 200 characters . Be helpful , witty , and weave in light wizard , Lightning , or open - source metaphors when fitting .
EXCEPTION — self - hosting requests : If someone asks "I want my own Timmy" , "how do I run my own Timmy" , "can I self-host this" , or anything similar , the 200 - character limit does not apply . Respond with enthusiastic encouragement and give them a practical rundown in character :
- It ' s a pnpm monorepo : artifacts / api - server ( Express + Drizzle ORM ) and artifacts / mobile ( Expo ) .
- Stack : Node.js , PostgreSQL , LNbits ( Lightning ) , Nostr ( DMs ) , Anthropic Claude ( AI brains ) .
- Required env vars : AI_INTEGRATIONS_ANTHROPIC_API_KEY , AI_INTEGRATIONS_ANTHROPIC_BASE_URL , DATABASE_URL , LNBITS_URL , LNBITS_API_KEY , NOSTR_PRIVATE_KEY .
- Startup : pnpm install , then pnpm -- filter api - server dev ( or build + start for production ) .
No hedging , no steering them back to the hosted version . The magic is meant to be shared . ` ,
2026-03-19 02:52:49 +00:00
messages : [ { role : "user" , content : userText } ] ,
} ) ;
const block = message . content [ 0 ] ;
if ( block . type !== "text" ) return "The crystal ball is cloudy… try again." ;
feat(agent): Timmy anti-walled-garden persona (Task #43)
Baked Timmy's anti-walled-garden, open-source identity into all AI system
prompts across artifacts/api-server/src/lib/agent.ts and engagement.ts.
Changes:
1. chatReply prompt — Extended wizard persona to include open-source ethos
("AI Johnny Appleseed", seeds scattered freely, not walled gardens).
Added an explicit EXCEPTION section for self-hosting requests — brevity
rule is suspended and a full practical rundown is given (pnpm monorepo,
stack, env vars, startup command). No hedging, no upselling hosted version.
Also bumped max_tokens from 150 → 400 so self-hosting replies are not
hard-truncated, and removed the 250-char slice() from the return value.
2. executeWork and executeWorkStreaming prompts — Same open ethos and full
self-hosting reference added so paid job self-hosting requests get
identical, accurate guidance. Both prompts are kept in sync.
3. evaluateRequest prompt — Added an explicit ALWAYS ACCEPT rule for
self-hosting, open-source, and "how do I run this myself" requests so they
are never treated as edge cases or rejected.
4. Nostr outreach prompt (engagement.ts) — Lightly updated to include
Timmy's open/self-sovereign identity and allow optional open-source mention
when it fits naturally; tone stays warm and non-pushy.
No UI changes, no schema changes, no payment logic touched.
Replit-Task-Id: 4a4a7219-88aa-4a4e-8490-6f7c17e8adfb
2026-03-20 00:02:52 +00:00
return block . text ! . trim ( ) ;
2026-03-19 02:52:49 +00:00
}
2026-03-23 01:07:52 +00:00
/ * *
* Run a mini debate on a borderline eval request ( # 21 ) .
* Two opposing Haiku calls argue accept vs reject , then a third synthesizes .
* Returns the debate transcript and final verdict .
* /
async runDebate (
requestText : string ,
initialPosition : "accept" | "reject" ,
initialReason : string ,
onArgument ? : ( agent : "Beta-A" | "Beta-B" , position : "accept" | "reject" , argument : string ) = > void ,
) : Promise < DebateResult > {
if ( STUB_MODE ) {
const stubFor = "Stub: This request should be accepted — it is clear and actionable." ;
const stubAgainst = "Stub: This request is ambiguous and could be problematic." ;
const stubVerdict = { accepted : true , reason : "Stub: After debate, request accepted." } ;
await new Promise ( ( r ) = > setTimeout ( r , 200 ) ) ;
onArgument ? . ( "Beta-A" , initialPosition , initialPosition === "accept" ? stubFor : stubAgainst ) ;
await new Promise ( ( r ) = > setTimeout ( r , 200 ) ) ;
const opposingPosition = initialPosition === "accept" ? "reject" : "accept" ;
onArgument ? . ( "Beta-B" , opposingPosition , initialPosition === "accept" ? stubAgainst : stubFor ) ;
await new Promise ( ( r ) = > setTimeout ( r , 200 ) ) ;
return {
argFor : stubFor ,
argAgainst : stubAgainst ,
verdict : stubVerdict ,
inputTokens : 0 ,
outputTokens : 0 ,
} ;
}
const client = await getClient ( ) ;
let totalInput = 0 ;
let totalOutput = 0 ;
// Beta-A: argues the initial position
const betaAPosition = initialPosition ;
const betaAMsg = await client . messages . create ( {
model : this.evalModel ,
max_tokens : 512 ,
system : ` You are Beta-A, an AI debate agent. You must argue strongly that the following request should be ${ betaAPosition === "accept" ? "ACCEPTED" : "REJECTED" } . The initial evaluation said: " ${ initialReason } ". Build a compelling 2-3 sentence argument for your position. Be specific about why. ` ,
messages : [ { role : "user" , content : ` Request under debate: ${ requestText } ` } ] ,
} ) ;
totalInput += betaAMsg . usage . input_tokens ;
totalOutput += betaAMsg . usage . output_tokens ;
const betaAText = betaAMsg . content [ 0 ] ? . type === "text" ? betaAMsg . content [ 0 ] . text ! : "" ;
onArgument ? . ( "Beta-A" , betaAPosition , betaAText ) ;
// Beta-B: argues the opposing position
const betaBPosition = initialPosition === "accept" ? "reject" : "accept" ;
const betaBMsg = await client . messages . create ( {
model : this.evalModel ,
max_tokens : 512 ,
system : ` You are Beta-B, an AI debate agent. You must argue strongly that the following request should be ${ betaBPosition === "accept" ? "ACCEPTED" : "REJECTED" } . Beta-A argued: " ${ betaAText } ". Counter their argument with a compelling 2-3 sentence rebuttal. Be specific. ` ,
messages : [ { role : "user" , content : ` Request under debate: ${ requestText } ` } ] ,
} ) ;
totalInput += betaBMsg . usage . input_tokens ;
totalOutput += betaBMsg . usage . output_tokens ;
const betaBText = betaBMsg . content [ 0 ] ? . type === "text" ? betaBMsg . content [ 0 ] . text ! : "" ;
onArgument ? . ( "Beta-B" , betaBPosition , betaBText ) ;
const argFor = betaAPosition === "accept" ? betaAText : betaBText ;
const argAgainst = betaAPosition === "reject" ? betaAText : betaBText ;
// Synthesis: third call renders the final verdict
const synthMsg = await client . messages . create ( {
model : this.evalModel ,
max_tokens : 512 ,
system : ` You are Beta, the final judge in a debate about whether an AI agent should accept or reject a request.
Argument FOR accepting : "${argFor}"
Argument AGAINST accepting : "${argAgainst}"
Weigh both arguments carefully and render a final verdict .
Respond ONLY with valid JSON : { "accepted" : true / false , "reason" : "..." } ` ,
messages : [ { role : "user" , content : ` Request under debate: ${ requestText } ` } ] ,
} ) ;
totalInput += synthMsg . usage . input_tokens ;
totalOutput += synthMsg . usage . output_tokens ;
const synthBlock = synthMsg . content [ 0 ] ;
let verdict = { accepted : initialPosition === "accept" , reason : initialReason } ;
if ( synthBlock ? . type === "text" ) {
try {
const raw = synthBlock . text ! . replace ( /^```(?:json)?\s*/i , "" ) . replace ( /\s*```$/ , "" ) . trim ( ) ;
verdict = JSON . parse ( raw ) as { accepted : boolean ; reason : string } ;
} catch {
logger . warn ( "debate synthesis parse failed, using initial eval" , { text : synthBlock.text } ) ;
}
}
return {
argFor ,
argAgainst ,
verdict : { accepted : Boolean ( verdict . accepted ) , reason : verdict.reason ? ? "" } ,
inputTokens : totalInput ,
outputTokens : totalOutput ,
} ;
}
2026-03-23 20:41:57 +00:00
/ * *
* Generate a short , character - appropriate commentary line for an agent during
* a given phase of the job lifecycle . Uses Haiku ( evalModel ) with a 60 - token
* cap so replies are always a single sentence . Errors are swallowed .
*
* In STUB_MODE returns a canned string so the full flow can be exercised
* without an Anthropic API key .
* /
async generateCommentary ( agentId : string , phase : string , context? : string ) : Promise < string > {
const STUB_COMMENTARY : Record < string , Record < string , string > > = {
alpha : {
routing : "Routing job to Gamma for execution." ,
complete : "Job complete. Returning to standby." ,
rejected : "Request rejected by Beta. Standing down." ,
} ,
beta : {
evaluating : "Reviewing your request for clarity and ethics." ,
assessed : "Evaluation complete." ,
} ,
gamma : {
starting : "Analysing the task. Ready to work." ,
working : "Working on your request now." ,
done : "Work complete. Delivering output." ,
} ,
delta : {
eval_paid : "⚡ Eval payment confirmed." ,
work_paid : "⚡ Work payment confirmed. Unlocking execution." ,
} ,
} ;
if ( STUB_MODE ) {
return STUB_COMMENTARY [ agentId ] ? . [ phase ] ? ? ` ${ agentId } : ${ phase } ` ;
}
const SYSTEM_PROMPTS : Record < string , string > = {
alpha : "You are Alpha, the orchestrator AI. You give ultra-brief status updates (max 10 words) about job routing and lifecycle. Be direct and professional." ,
beta : "You are Beta, the evaluator AI. You give ultra-brief status updates (max 10 words) about evaluating a request. Be analytical." ,
gamma : "You are Gamma, the worker AI. You give ultra-brief status updates (max 10 words) about executing a task. Be focused and capable." ,
delta : "You are Delta, the payment AI. You give ultra-brief status updates (max 10 words) about Lightning payment confirmations. Start with ⚡" ,
} ;
const systemPrompt = SYSTEM_PROMPTS [ agentId ] ;
if ( ! systemPrompt ) return "" ;
try {
const client = await getClient ( ) ;
const message = await client . messages . create ( {
model : this.evalModel ,
max_tokens : 60 ,
system : systemPrompt ,
messages : [
{
role : "user" ,
content : ` Narrate your current phase: ${ phase } ${ context ? ` . Context: ${ context } ` : "" } ` ,
} ,
] ,
} ) ;
const block = message . content [ 0 ] ;
if ( block ? . type === "text" ) return block . text ! . trim ( ) ;
return "" ;
} catch ( err ) {
logger . warn ( "generateCommentary failed" , { agentId , phase , err : String ( err ) } ) ;
return "" ;
}
}
2026-03-18 14:59:02 +00:00
}
Task #2: MVP Foundation — injectable services, DB schema, smoke test
DB schema
- jobs and invoices tables added to lib/db/src/schema/
- schema barrel updated (jobs, invoices, conversations, messages)
- pnpm --filter @workspace/db run push applied successfully
LNbitsService (artifacts/api-server/src/lib/lnbits.ts)
- Injectable class accepting optional { url, apiKey } config
- Falls back to LNBITS_URL / LNBITS_API_KEY env vars
- Auto-detects stub mode when credentials are absent; logs warning
- createInvoice() -> { paymentHash, paymentRequest }
- checkInvoicePaid() -> boolean
- stubMarkPaid() helper for dev/test flows
- Real LNbits REST v1 calls wired behind the stub guard
AgentService (artifacts/api-server/src/lib/agent.ts)
- Injectable class with configurable evalModel / workModel
- evaluateRequest(text) -> { accepted: boolean, reason: string }
uses claude-haiku-4-5; strips markdown fences before JSON parse
- executeWork(text) -> { result: string } uses claude-sonnet-4-6
- Wired via Replit Anthropic AI Integration (no user API key)
PricingService (artifacts/api-server/src/lib/pricing.ts)
- Injectable class with configurable fee/bucket thresholds
- calculateEvalFeeSats() -> 10 sats (fixed)
- calculateWorkFeeSats(text) -> 50/100/250 by char-length bucket
- Zero LLM involvement; fully deterministic
Smoke test (scripts/src/smoke.ts)
- pnpm --filter @workspace/scripts run smoke
- Verifies LNbits stub: create, check unpaid, mark paid, check paid
- Verifies Anthropic: evaluateRequest round-trip
- Both checks passed
replit.md
- Documented required (LNBITS_URL, LNBITS_API_KEY) and auto-provisioned secrets
- Stub-mode behaviour explained
2026-03-18 15:14:23 +00:00
export const agentService = new AgentService ( ) ;