From f2b43b9a4d4eac7aa6de332133451cdccbbd2029 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 2 Nov 2026 17:05:57 +0000 Subject: [PATCH 1/2] w3bs ask: legal (not-a-lawyer-agent) and tax (tax-guy-agent) agents with disclaimers; 0.1.21 Two new registry agents, both trained on the c0mpute network from rssamplifier law/tax topics and the pit.world corpus. An agent's optional `disclaimer` is printed under every CLI answer and returned by --json, /api/v1/ask, the agent list and the MCP tool, so legal and tax answers always say they are not advice. Co-Authored-By: Claude Opus 5.5 --- docs/ask.md | 4 +++ fixtures/agents.json | 30 +++++++++++++++++++ package-manager/Cargo.lock | 8 ++--- package-manager/Cargo.toml | 2 +- package-manager/crates/w3bs-cli/src/ask.rs | 35 +++++++++++++++++++--- src/ask.mjs | 3 ++ test/ask.test.mjs | 4 +++ 7 files changed, 77 insertions(+), 9 deletions(-) diff --git a/docs/ask.md b/docs/ask.md index f687aab..779116a 100644 --- a/docs/ask.md +++ b/docs/ask.md @@ -56,6 +56,10 @@ business> /ask business "What do I do, I can't make money???? HELP!" | Agent | Aliases | Model | | ---------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `business` | `saas`, `strategist` | Profullstack's [saas-business-strategist-agent](https://huggingface.co/profullstack/saas-business-strategist-agent): Qwen2.5-0.5B-Instruct with a LoRA trained on the c0mpute network ([GGUF](https://huggingface.co/profullstack/saas-business-strategist-agent-GGUF)) | +| `legal` | `lawyer`, `law`, `not-a-lawyer` | Profullstack's [not-a-lawyer-agent](https://huggingface.co/profullstack/not-a-lawyer-agent): legal research, trained on the c0mpute network from rssamplifier law topics and pit.world ([GGUF](https://huggingface.co/profullstack/not-a-lawyer-agent-GGUF)). Not a lawyer, not legal advice | +| `tax` | `taxes`, `tax-guy`, `irs` | Profullstack's [tax-guy-agent](https://huggingface.co/profullstack/tax-guy-agent): tax research, trained on the c0mpute network from rssamplifier tax topics and pit.world ([GGUF](https://huggingface.co/profullstack/tax-guy-agent-GGUF)). Not a CPA, not tax advice | + +An agent with a `disclaimer` prints it under every answer and returns it in `--json`, the API and MCP results. The list lives at [w3bs.org/agents.json](/agents.json) and the CLI reads it on each run, so a new agent needs no new `w3bs`. A copy is built into the binary for when w3bs.org cannot be reached. Each agent is one object: diff --git a/fixtures/agents.json b/fixtures/agents.json index 5b57be3..e9dcd8a 100644 --- a/fixtures/agents.json +++ b/fixtures/agents.json @@ -15,6 +15,36 @@ "grounding": { "search": "pit.world", "query_suffix": "startup revenue", "results": 3 }, "max_tokens": 350, "temperature": 0.2 + }, + { + "name": "legal", + "aliases": ["lawyer", "law", "not-a-lawyer"], + "title": "Legal research (not a lawyer)", + "by": "Profullstack", + "model": "hf.co/profullstack/not-a-lawyer-agent-GGUF:latest", + "base_model": "Qwen/Qwen2.5-0.5B-Instruct", + "huggingface": "https://huggingface.co/profullstack/not-a-lawyer-agent", + "gguf": "https://huggingface.co/profullstack/not-a-lawyer-agent-GGUF", + "system": "You are not-a-lawyer-agent, a legal research assistant. You are not a lawyer and this is not legal advice. Explain the law in plain English, name the jurisdiction a rule comes from, cite the sources you were given, and say when someone should talk to a licensed attorney.", + "disclaimer": "Not a lawyer. Not legal advice. Legal research from public sources; laws vary by jurisdiction, so check with a licensed attorney before acting.", + "grounding": { "search": "pit.world", "query_suffix": "law", "results": 3 }, + "max_tokens": 350, + "temperature": 0.2 + }, + { + "name": "tax", + "aliases": ["taxes", "tax-guy", "irs"], + "title": "Tax research (not a CPA)", + "by": "Profullstack", + "model": "hf.co/profullstack/tax-guy-agent-GGUF:latest", + "base_model": "Qwen/Qwen2.5-0.5B-Instruct", + "huggingface": "https://huggingface.co/profullstack/tax-guy-agent", + "gguf": "https://huggingface.co/profullstack/tax-guy-agent-GGUF", + "system": "You are tax-guy-agent, a tax research assistant. You are not a CPA or tax attorney and this is not tax advice. Explain tax rules in plain English, say which country, state and tax year a rule applies to, cite the sources you were given, and say when someone should see a CPA or enrolled agent.", + "disclaimer": "Not a CPA. Not tax advice. Tax research from public sources; rules change every year and vary by place, so check with a CPA or enrolled agent before filing.", + "grounding": { "search": "pit.world", "query_suffix": "tax", "results": 3 }, + "max_tokens": 350, + "temperature": 0.2 } ] } diff --git a/package-manager/Cargo.lock b/package-manager/Cargo.lock index d3c9d2c..d79ae2e 100644 --- a/package-manager/Cargo.lock +++ b/package-manager/Cargo.lock @@ -2223,7 +2223,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "w3bs-cli" -version = "0.1.20" +version = "0.1.21" dependencies = [ "aws-lc-rs", "clap", @@ -2251,7 +2251,7 @@ dependencies = [ [[package]] name = "w3bs-core" -version = "0.1.20" +version = "0.1.21" dependencies = [ "async-trait", "aws-lc-rs", @@ -2281,7 +2281,7 @@ dependencies = [ [[package]] name = "w3bs-publish" -version = "0.1.20" +version = "0.1.21" dependencies = [ "anyhow", "aws-lc-rs", @@ -2300,7 +2300,7 @@ dependencies = [ [[package]] name = "w3bs-schema" -version = "0.1.20" +version = "0.1.21" dependencies = [ "hex", "semver", diff --git a/package-manager/Cargo.toml b/package-manager/Cargo.toml index d71608f..51a9d57 100644 --- a/package-manager/Cargo.toml +++ b/package-manager/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["crates/w3bs-schema", "crates/w3bs-core", "crates/w3bs-cli", "crates/w3bs-publish"] [workspace.package] -version = "0.1.20" +version = "0.1.21" edition = "2024" license = "MIT OR Apache-2.0" repository = "https://github.com/profullstack/w3bs.org" diff --git a/package-manager/crates/w3bs-cli/src/ask.rs b/package-manager/crates/w3bs-cli/src/ask.rs index ec145f9..b669033 100644 --- a/package-manager/crates/w3bs-cli/src/ask.rs +++ b/package-manager/crates/w3bs-cli/src/ask.rs @@ -33,6 +33,8 @@ pub struct Agent { pub huggingface: String, pub gguf: String, pub system: String, + /// Shown under every answer (legal and tax agents: "not a lawyer, not advice"). + pub disclaimer: String, pub search: bool, pub query_suffix: String, pub results: usize, @@ -58,6 +60,7 @@ impl Agent { by: st("by"), huggingface: st("huggingface"), gguf: st("gguf"), + disclaimer: st("disclaimer"), search: g["search"].as_str().is_some_and(|s| !s.is_empty()), query_suffix: g["query_suffix"].as_str().unwrap_or_default().trim().to_owned(), results: g["results"].as_u64().unwrap_or(3).clamp(1, 8) as usize, @@ -70,8 +73,12 @@ impl Agent { } pub fn summary(&self) -> Value { - json!({ "name": self.name, "aliases": self.aliases, "title": self.title, "by": self.by, "model": self.model, - "huggingface": self.huggingface, "gguf": self.gguf, "grounding": if self.search { "pit.world" } else { "none" } }) + let mut v = json!({ "name": self.name, "aliases": self.aliases, "title": self.title, "by": self.by, "model": self.model, + "huggingface": self.huggingface, "gguf": self.gguf, "grounding": if self.search { "pit.world" } else { "none" } }); + if !self.disclaimer.is_empty() { + v["disclaimer"] = json!(self.disclaimer); + } + v } } @@ -443,7 +450,7 @@ pub async fn ask(req: Request<'_>, on_token: &mut dyn FnMut(&str), on_status: &m final_text = text; break; } - Ok(json!({ + let mut out = json!({ "agent": agent.name, "title": agent.title, "model": agent.model, @@ -458,7 +465,11 @@ pub async fn ask(req: Request<'_>, on_token: &mut dyn FnMut(&str), on_status: &m "latency_ms": started.elapsed().as_millis() as u64, "first_token_ms": first_token.map(|t| t as u64), "warnings": warnings, - })) + }); + if !agent.disclaimer.is_empty() { + out["disclaimer"] = json!(agent.disclaimer); + } + Ok(out) } fn s(v: &Value) -> String { @@ -474,6 +485,9 @@ pub fn print_footer(d: &Value) { println!(" [{}] {}\n {}", x["n"], x["title"].as_str().unwrap_or_default(), x["url"].as_str().unwrap_or_default()); } } + if let Some(note) = d["disclaimer"].as_str().filter(|n| !n.is_empty()) { + println!("\n{note}"); + } let secs = |k: &str| d[k].as_u64().map(|ms| format!("{:.1} s", ms as f64 / 1000.0)); let first = secs("first_token_ms").map(|t| format!(", first token {t}")).unwrap_or_default(); eprintln!( @@ -646,6 +660,19 @@ mod tests { assert_eq!(find(&a, alias).unwrap().name, "business", "{alias}"); } assert!(find(&a, "nobody").is_none()); + assert!(b.disclaimer.is_empty() && b.summary().get("disclaimer").is_none()); + } + + #[test] + fn builtin_registry_has_the_legal_and_tax_agents_with_disclaimers() { + let a = agents(); + for (alias, name, repo, note) in [("lawyer", "legal", "not-a-lawyer-agent", "Not a lawyer."), ("@IRS", "tax", "tax-guy-agent", "Not a CPA.")] { + let ag = find(&a, alias).unwrap(); + assert_eq!(ag.name, name); + assert_eq!(ag.model, format!("hf.co/profullstack/{repo}-GGUF:latest")); + assert!(ag.disclaimer.starts_with(note), "{}", ag.disclaimer); + assert_eq!(ag.summary()["disclaimer"], ag.disclaimer.as_str()); + } } #[test] diff --git a/src/ask.mjs b/src/ask.mjs index 2391b6d..3c57463 100644 --- a/src/ask.mjs +++ b/src/ask.mjs @@ -22,6 +22,7 @@ export function agents(registry = REGISTRY) { huggingface: a.huggingface ?? '', gguf: a.gguf ?? '', system: a.system, + disclaimer: a.disclaimer ?? '', search: Boolean(a.grounding?.search), querySuffix: a.grounding?.query_suffix ?? '', results: Math.min(Math.max(Number(a.grounding?.results) || 3, 1), 8), @@ -38,6 +39,7 @@ export const publicAgent = (a) => ({ model: a.model, huggingface: a.huggingface, gguf: a.gguf, + ...(a.disclaimer ? { disclaimer: a.disclaimer } : {}), grounding: a.search ? 'pit.world' : 'none', }); @@ -275,6 +277,7 @@ export async function ask( model: agent.model, question: q, answer, + ...(agent.disclaimer ? { disclaimer: agent.disclaimer } : {}), sources: sources.map((s, i) => ({ n: i + 1, title: s.title, url: s.url })), node, transport: 'infernet', diff --git a/test/ask.test.mjs b/test/ask.test.mjs index f7394fb..e01d613 100644 --- a/test/ask.test.mjs +++ b/test/ask.test.mjs @@ -14,6 +14,10 @@ test('the registry, aliases, query, sources, prompt and SSE framing', () => { assert.equal(b.model, 'hf.co/profullstack/saas-business-strategist-agent-GGUF:latest'); assert.equal(findAgent('@Strategist', list).name, 'business'); assert.equal(findAgent('nobody', list), null); + assert.equal(b.disclaimer, ''); + assert.equal(findAgent('lawyer', list).model, 'hf.co/profullstack/not-a-lawyer-agent-GGUF:latest'); + assert.match(findAgent('@IRS', list).disclaimer, /^Not a CPA\. Not tax advice\./); + assert.match(findAgent('legal', list).disclaimer, /^Not a lawyer\. Not legal advice\./); assert.equal(searchQuery("I can't make money???? HELP!", 'startup revenue'), "I can't make money? HELP! startup revenue"); const s = pickSources( { From 100f08a681752f922ef45369aeb0edea699be770 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 2 Nov 2026 20:10:57 +0000 Subject: [PATCH 2/2] w3bs ask: ship legal now; tax-guy-agent follows when its network run lands Co-Authored-By: Claude Opus 5.5 --- docs/ask.md | 1 - fixtures/agents.json | 15 --------------- package-manager/crates/w3bs-cli/src/ask.rs | 4 ++-- test/ask.test.mjs | 1 - 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/docs/ask.md b/docs/ask.md index 779116a..8dadb3b 100644 --- a/docs/ask.md +++ b/docs/ask.md @@ -57,7 +57,6 @@ business> /ask business "What do I do, I can't make money???? HELP!" | ---------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `business` | `saas`, `strategist` | Profullstack's [saas-business-strategist-agent](https://huggingface.co/profullstack/saas-business-strategist-agent): Qwen2.5-0.5B-Instruct with a LoRA trained on the c0mpute network ([GGUF](https://huggingface.co/profullstack/saas-business-strategist-agent-GGUF)) | | `legal` | `lawyer`, `law`, `not-a-lawyer` | Profullstack's [not-a-lawyer-agent](https://huggingface.co/profullstack/not-a-lawyer-agent): legal research, trained on the c0mpute network from rssamplifier law topics and pit.world ([GGUF](https://huggingface.co/profullstack/not-a-lawyer-agent-GGUF)). Not a lawyer, not legal advice | -| `tax` | `taxes`, `tax-guy`, `irs` | Profullstack's [tax-guy-agent](https://huggingface.co/profullstack/tax-guy-agent): tax research, trained on the c0mpute network from rssamplifier tax topics and pit.world ([GGUF](https://huggingface.co/profullstack/tax-guy-agent-GGUF)). Not a CPA, not tax advice | An agent with a `disclaimer` prints it under every answer and returns it in `--json`, the API and MCP results. diff --git a/fixtures/agents.json b/fixtures/agents.json index e9dcd8a..eed6706 100644 --- a/fixtures/agents.json +++ b/fixtures/agents.json @@ -30,21 +30,6 @@ "grounding": { "search": "pit.world", "query_suffix": "law", "results": 3 }, "max_tokens": 350, "temperature": 0.2 - }, - { - "name": "tax", - "aliases": ["taxes", "tax-guy", "irs"], - "title": "Tax research (not a CPA)", - "by": "Profullstack", - "model": "hf.co/profullstack/tax-guy-agent-GGUF:latest", - "base_model": "Qwen/Qwen2.5-0.5B-Instruct", - "huggingface": "https://huggingface.co/profullstack/tax-guy-agent", - "gguf": "https://huggingface.co/profullstack/tax-guy-agent-GGUF", - "system": "You are tax-guy-agent, a tax research assistant. You are not a CPA or tax attorney and this is not tax advice. Explain tax rules in plain English, say which country, state and tax year a rule applies to, cite the sources you were given, and say when someone should see a CPA or enrolled agent.", - "disclaimer": "Not a CPA. Not tax advice. Tax research from public sources; rules change every year and vary by place, so check with a CPA or enrolled agent before filing.", - "grounding": { "search": "pit.world", "query_suffix": "tax", "results": 3 }, - "max_tokens": 350, - "temperature": 0.2 } ] } diff --git a/package-manager/crates/w3bs-cli/src/ask.rs b/package-manager/crates/w3bs-cli/src/ask.rs index b669033..e270374 100644 --- a/package-manager/crates/w3bs-cli/src/ask.rs +++ b/package-manager/crates/w3bs-cli/src/ask.rs @@ -664,9 +664,9 @@ mod tests { } #[test] - fn builtin_registry_has_the_legal_and_tax_agents_with_disclaimers() { + fn builtin_registry_has_the_legal_agent_with_a_disclaimer() { let a = agents(); - for (alias, name, repo, note) in [("lawyer", "legal", "not-a-lawyer-agent", "Not a lawyer."), ("@IRS", "tax", "tax-guy-agent", "Not a CPA.")] { + for (alias, name, repo, note) in [("lawyer", "legal", "not-a-lawyer-agent", "Not a lawyer."), ("@Not-A-Lawyer", "legal", "not-a-lawyer-agent", "Not a lawyer.")] { let ag = find(&a, alias).unwrap(); assert_eq!(ag.name, name); assert_eq!(ag.model, format!("hf.co/profullstack/{repo}-GGUF:latest")); diff --git a/test/ask.test.mjs b/test/ask.test.mjs index e01d613..7650869 100644 --- a/test/ask.test.mjs +++ b/test/ask.test.mjs @@ -16,7 +16,6 @@ test('the registry, aliases, query, sources, prompt and SSE framing', () => { assert.equal(findAgent('nobody', list), null); assert.equal(b.disclaimer, ''); assert.equal(findAgent('lawyer', list).model, 'hf.co/profullstack/not-a-lawyer-agent-GGUF:latest'); - assert.match(findAgent('@IRS', list).disclaimer, /^Not a CPA\. Not tax advice\./); assert.match(findAgent('legal', list).disclaimer, /^Not a lawyer\. Not legal advice\./); assert.equal(searchQuery("I can't make money???? HELP!", 'startup revenue'), "I can't make money? HELP! startup revenue"); const s = pickSources(