docs(readme,landing): lead the story with automatic agent building (#52)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei Zheng
2026-07-24 00:57:28 +08:00
committed by GitHub
parent 33e5f0bcd1
commit 6bbdac132f
15 changed files with 229 additions and 47 deletions
+9 -9
View File
@@ -4,9 +4,7 @@
<h1 align="center">PenguinHarness</h1>
<p align="center"><b>With LangChain, you build agents by hand — at 1× speed.<br />With PenguinHarness, agents build agents — at 100×.</b></p>
<p align="center">A zero-code CLI and Web UI for building agents, connected to 1,000+ models.</p>
<p align="center"><b>Your automated agent builder lives on your desktop, connected to 1,000+ models.</b></p>
<p align="center">
<a href="https://www.producthunt.com/products/penguinharness?embed=true&amp;utm_source=badge-featured&amp;utm_medium=badge&amp;utm_campaign=badge-penguinharness" target="_blank" rel="noopener noreferrer"><img alt="PenguinHarness - Let Agents Autonomously Build Better Agents for $0.02 | Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1202577&amp;theme=light&amp;t=1784804711946" /></a>
@@ -36,9 +34,11 @@
## Why PenguinHarness
> With LangChain, you build agents by hand — at 1× speed.<br />With PenguinHarness, agents build agents — at 100×.
Three reasons, in deliberate order — from task quality, to how agents get built, to how they keep improving.
### 1. 🏆 Comparable quality, one to two orders of magnitude cheaper
### 1. 🏆 Outstanding results at tens of times less cost
A deliberately minimal toolset over clean low-level interfaces: fewer tool calls, fewer tokens — deeply tuned for open models like DeepSeek. Each harness on the model it is normally paired with, same tasks, head-to-head:
@@ -51,9 +51,9 @@ A deliberately minimal toolset over clean low-level interfaces: fewer tool calls
**Best accuracy on data analysis — at 1/70 of Claude Code's cost.**
### 2. ⚡ One sentence, and an Agent builds your Agent app
### 2. ⚡ One sentence, and an agent builds your agent app
Type one sentence, and an Agent builds the complete Agent application for you — scaffold, code, and run instructions, end to end:
Type one sentence, and an agent builds the complete agent application for you — scaffold, code, and run instructions, end to end:
```text
Collect the docs from https://github.com/ericbuess/claude-code-docs and build a RAG app that answers Claude Code questions as a configuration expert, citing its sources.
@@ -67,13 +67,13 @@ https://github.com/user-attachments/assets/9b7033e8-f08a-4c3f-bd33-547896664e6e
### 3. 🧬 Self-evolution: it gets stronger with use
With PenguinHarness Skills, an Agent evaluates and optimizes itself: run the benchmark, find the lost points, ship version N+1 — with a snapshot before every round, and every request observable in the Trace view.
With PenguinHarness Skills, an agent evaluates and optimizes itself: run the benchmark, find the lost points, ship version N+1 — with a snapshot before every round, and every request observable in the Trace view.
https://github.com/user-attachments/assets/922d13a6-5ffc-4685-9a39-352f02f9afc0
## Built-in Skills
Four Skill groups ship in the box ([docs](https://penguin.ooo/docs/skills)); Agents can also write and optimize their own:
Four Skill groups ship in the box ([docs](https://penguin.ooo/docs/skills)); agents can also write and optimize their own:
| Group | Skills |
| -------------------- | --------------------------------------------------------------------------------- |
@@ -110,7 +110,7 @@ Each family's latest generation only — the app's **Models** page lists every b
### 🌐 Web App — for humans
🚀 Install and launch the full experience (multi-session chat, Agent/skill/model management, usage stats, Trace observability, evaluation center):
🚀 Install and launch the full experience (multi-session chat, agent/skill/model management, usage stats, Trace observability, evaluation center):
```bash
curl -fsSL https://penguin.ooo/install.sh | sh
+4 -4
View File
@@ -4,9 +4,7 @@
<h1 align="center">PenguinHarness</h1>
<p align="center"><b>使用 LangChain,以 1 倍速度人工构建 Agent;<br />使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。</b></p>
<p align="center">用于构建 Agent 的零代码 CLI 与 Web UI,连接 1,000+ 模型。</p>
<p align="center"><b>全自动 Agent 构建器,住在你的桌面上,连接 1,000+ 模型。</b></p>
<p align="center">
<a href="https://www.producthunt.com/products/penguinharness?embed=true&amp;utm_source=badge-featured&amp;utm_medium=badge&amp;utm_campaign=badge-penguinharness" target="_blank" rel="noopener noreferrer"><img alt="PenguinHarness - Let Agents Autonomously Build Better Agents for $0.02 | Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1202577&amp;theme=light&amp;t=1784804711946" /></a>
@@ -36,9 +34,11 @@
## 为什么选择 PenguinHarness
> 使用 LangChain,以 1 倍速度人工构建 Agent;<br />使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。
三个递进的理由——从任务效果,到构建方式,再到进化能力。
### 1. 🏆 效果同级,成本低一到两个数量级
### 1. 🏆 以几十分之一的成本,跑出优异的效果
刻意精简的工具集配合干净的底层接口:更少的工具调用、更少的 Token,对 DeepSeek 等开放模型深度适配。各自搭配常用模型、同一批任务,正面对比:
+12 -2
View File
@@ -20,7 +20,15 @@ import { GitHubIcon, MenuIcon, XIcon } from "./icons";
import { ThemeToggle } from "./theme-toggle";
import { LangToggle } from "./lang-toggle";
const SECTION_IDS = ["highlights", "quickstart", "benchmark", "contract", "features"] as const;
const SECTION_IDS = [
"highlights",
"quickstart",
"cases",
"scenarios",
"benchmark",
"contract",
"features",
] as const;
export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMenu: () => void }) {
const pillRef = useRef<HTMLSpanElement | null>(null);
@@ -29,6 +37,8 @@ export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMen
const sectionLabel: Record<(typeof SECTION_IDS)[number], string> = {
highlights: S.nav.highlights,
quickstart: S.nav.quickstart,
cases: S.nav.cases,
scenarios: S.nav.scenarios,
benchmark: S.nav.benchmark,
contract: S.nav.contract,
features: S.nav.features,
@@ -102,7 +112,7 @@ export function Nav({ menuOpen, onToggleMenu }: { menuOpen: boolean; onToggleMen
</a>
<nav
className="relative ml-4 hidden items-center gap-0.5 md:flex"
className="relative ml-4 hidden items-center gap-0.5 xl:flex"
aria-label="Primary"
onMouseLeave={hidePill}
>
+2
View File
@@ -7,6 +7,8 @@ export const en: Strings = {
nav: {
highlights: "Highlights",
quickstart: "Quick start",
cases: "Cases",
scenarios: "Scenarios",
benchmark: "Benchmark",
contract: "CONTRACT.md",
features: "Features",
+2
View File
@@ -13,6 +13,8 @@ export const zh = {
// (same items, same order) so the two sites link into each other seamlessly.
highlights: "特色",
quickstart: "快速开始",
cases: "案例",
scenarios: "应用场景",
benchmark: "评测",
contract: "CONTRACT.md",
features: "功能",
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+5 -3
View File
@@ -37,6 +37,8 @@ export function Nav() {
const sectionLabel: Record<(typeof SECTION_IDS)[number], string> = {
highlights: S.nav.highlights,
quickstart: S.nav.quickstart,
cases: S.nav.cases,
scenarios: S.nav.scenarios,
benchmark: S.nav.benchmark,
contract: S.nav.contract,
features: S.nav.features,
@@ -152,7 +154,7 @@ export function Nav() {
</Link>
<nav
className="relative ml-4 hidden items-center gap-0.5 md:flex"
className="relative ml-4 hidden items-center gap-0.5 xl:flex"
aria-label="Primary"
onMouseLeave={hidePill}
>
@@ -184,7 +186,7 @@ export function Nav() {
onClick={() => setOpen((v) => !v)}
aria-label={open ? S.nav.closeMenu : S.nav.openMenu}
aria-expanded={open}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-transparent text-gray-600 transition-colors hover:border-gray-200 hover:bg-gray-50 md:hidden dark:text-gray-400 dark:hover:border-gray-800 dark:hover:bg-gray-900"
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-transparent text-gray-600 transition-colors hover:border-gray-200 hover:bg-gray-50 xl:hidden dark:text-gray-400 dark:hover:border-gray-800 dark:hover:bg-gray-900"
>
{open ? <XIcon className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
</button>
@@ -193,7 +195,7 @@ export function Nav() {
{open && (
<nav
className="anim-fade border-t border-gray-200 bg-white px-4 py-2 md:hidden dark:border-gray-800 dark:bg-gray-950"
className="anim-fade border-t border-gray-200 bg-white px-4 py-2 xl:hidden dark:border-gray-800 dark:bg-gray-950"
aria-label="Mobile"
>
<div className="flex flex-col py-1">{mobileLinks}</div>
+9 -2
View File
@@ -13,7 +13,8 @@ export function Section({
id?: string;
eyebrow?: string;
title?: string;
subtitle?: string;
/** A list renders one line per entry (deliberate breaks, e.g. the 1×/100× pair); a string wraps naturally. */
subtitle?: string | readonly string[];
children: ReactNode;
className?: string;
}) {
@@ -35,7 +36,13 @@ export function Section({
)}
{subtitle && (
<p className="mt-4 text-base leading-7 text-pretty text-gray-600 dark:text-gray-400">
{subtitle}
{typeof subtitle === "string"
? subtitle
: subtitle.map((line) => (
<span key={line} className="block">
{line}
</span>
))}
</p>
)}
</div>
+2
View File
@@ -5,6 +5,8 @@
export const SECTION_IDS = [
"highlights",
"quickstart",
"cases",
"scenarios",
"benchmark",
"contract",
"features",
+31 -7
View File
@@ -20,6 +20,8 @@ export const en: Strings = {
nav: {
highlights: "Highlights",
quickstart: "Quick start",
cases: "Cases",
scenarios: "Scenarios",
benchmark: "Benchmark",
contract: "CONTRACT.md",
features: "Features",
@@ -45,11 +47,13 @@ export const en: Strings = {
},
hero: {
badge: "Build AI agents, with an Agent",
titlePrefix: "Efficient Self-Improving Harness for ",
titleWords: ["Developers", "Enterprises"],
badge: "Agents building agents",
// The trailing space lives in the PREFIX (a breakable space before the nowrap
// span) — inside titleNoWrap it would glue "Builder" to the unbreakable chunk.
titlePrefix: "Your Automated Agent Builder ",
titleNoWrap: "Lives on Your ",
titleWords: ["Desktop", "Server"],
titleSuffix: "",
titleSuffixNoWrap: "",
keywords: ["Lightweight", "Efficient", "Open Source"],
ctaPrimary: "Get started",
ctaGithub: "GitHub",
@@ -99,8 +103,10 @@ export const en: Strings = {
compare: {
eyebrow: "vs. LangChain",
title: "The gap between 1× and 100×",
subtitle:
"With LangChain, you build agents by hand — at 1× speed. With PenguinHarness, agents build agents — at 100×.",
subtitle: [
"With LangChain, you build agents by hand — at 1× speed.",
"With PenguinHarness, agents build agents — at 100×.",
],
langchain: {
name: "LangChain",
speed: "1×",
@@ -212,6 +218,24 @@ export const en: Strings = {
],
},
scenarios: {
eyebrow: "Scenarios",
title: "From screening centers to factory floors",
subtitle: "Real deployments in production — not concept demos.",
items: [
{
title: "Screening-report QC",
alt: "A CT scanner room at a health screening center",
body: "A health-screening group handed report QC to an agent on a locally deployed Qwen3 14B — data never leaves the facility. A review round that took 30 minutes by hand now clears 30 reports a minute, with findings in line with medical experts and several times the review capacity.",
},
{
title: "Production-line inspection",
alt: "An automated semiconductor production line",
body: "A manufacturer runs inspection agents across its production lines, watching equipment around the clock and trying automated recovery first when something goes wrong. Downtime is down 65%; output roughly doubled.",
},
],
},
contract: {
eyebrow: "A contract for stable evolution",
title: "CONTRACT.md",
@@ -266,7 +290,7 @@ export const en: Strings = {
benchmark: {
eyebrow: "Benchmark",
title: "Each with its flagship model — comparable quality, 1-2 orders of magnitude cheaper",
title: "Outstanding results at tens of times less cost",
subtitle:
"Every product runs the model it is normally paired with, head-to-head against Claude Code and OpenAI Codex on two suites: comparable accuracy, tens of times the difference in spend.",
higherBetter: "higher is better",
+33 -10
View File
@@ -21,6 +21,8 @@ export const zh = {
nav: {
highlights: "特色",
quickstart: "快速开始",
cases: "案例",
scenarios: "应用场景",
benchmark: "评测",
contract: "CONTRACT.md",
features: "功能",
@@ -48,14 +50,15 @@ export const zh = {
hero: {
badge: "让 Agent 为你构建 Agent",
/**
* Rotating headline: {titlePrefix}{word}{titleSuffix}{titleSuffixNoWrap} — the
* word cycles through titleWords with a gaussian-blur crossfade; titleSuffixNoWrap
* renders as an unbreakable span so CJK line-breaking lands on the phrase boundary.
* Headline: {titlePrefix}<nowrap>{titleNoWrap}{word}{titleSuffix}</nowrap>, the word
* rotating through titleWords (桌面/服务器 — where the builder lives; the README
* states Desktop only). The nowrap span pins the wrap point in front of it, so a
* break never splits "Agent Builder" / "Lives on Your Desktop" mid-phrase.
*/
titlePrefix: "专为",
titleWords: ["开发者", "企业"],
titleSuffix: "设计的",
titleSuffixNoWrap: "高效自进化 Harness",
titlePrefix: "全自动 Agent 构建器,",
titleNoWrap: "住在你的",
titleWords: ["桌面", "服务器"],
titleSuffix: "上",
keywords: ["轻量", "高效", "开源"],
ctaPrimary: "快速开始",
ctaGithub: "GitHub",
@@ -103,8 +106,10 @@ export const zh = {
compare: {
eyebrow: "对比 LangChain",
title: "1× 与 100× 的差距",
subtitle:
"使用 LangChain,以 1 倍速度人工构建 Agent;使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。",
subtitle: [
"使用 LangChain,以 1 倍速度人工构建 Agent;",
"使用 PenguinHarness,以 100 倍速度用 Agent 构建 Agent。",
],
langchain: {
name: "LangChain",
speed: "1×",
@@ -208,6 +213,24 @@ export const zh = {
],
},
scenarios: {
eyebrow: "应用场景",
title: "从体检中心到工厂车间",
subtitle: "都是已经上线的真实部署,不是概念演示。",
items: [
{
title: "体检报告质控",
alt: "体检中心的 CT 检查室",
body: "一家体检机构把报告质控交给了 Agent,跑在本地部署的 Qwen3 14B 上,数据不出机房。过去一轮人工复核要 30 分钟,现在一分钟自动过 30 份,核查结论与医学专家基本一致,审核产能提升数倍。",
},
{
title: "产线设备巡检",
alt: "自动化半导体生产线",
body: "一家制造企业在各条流水线上部署巡检 Agent,全天候盯着设备状态,异常时先尝试自动恢复。停机时间减少 65%,产出提升约 2 倍。",
},
],
},
contract: {
eyebrow: "稳定进化的契约",
title: "CONTRACT.md",
@@ -260,7 +283,7 @@ export const zh = {
benchmark: {
eyebrow: "Benchmark",
title: "各用各的旗舰模型,效果同级,成本低一到两个数量级",
title: "以几十分之一的成本,跑出优异的效果",
subtitle:
"每个产品搭配它常用的模型,与 Claude Code、OpenAI Codex 在两套题库上正面对比:准确率同级,花的钱差出几十倍。",
higherBetter: "越高越好",
+2
View File
@@ -5,6 +5,7 @@ import { Compare } from "../sections/compare";
import { SelfImprove } from "../sections/self-improve";
import { Quickstart } from "../sections/quickstart";
import { Cases } from "../sections/cases";
import { Scenarios } from "../sections/scenarios";
import { Benchmark } from "../sections/benchmark";
import { Contract } from "../sections/contract";
import { Features } from "../sections/features";
@@ -22,6 +23,7 @@ export function HomePage() {
<SelfImprove />
<Quickstart />
<Cases />
<Scenarios />
<Benchmark />
<Contract />
<Features />
+13 -10
View File
@@ -1,8 +1,8 @@
/**
* Hero: enlarged logo + product name, the slogan inside the pill badge, then a
* bilingual headline whose rotating word crossfades through a gaussian blur
* (Developers <-> Enterprises), three keywords, the install one-liner and stats.
* The rotating word is a stacked inline-grid so line width never jumps.
* Hero: enlarged logo + product name, the slogan inside the pill badge, then the
* one-line headline whose rotating word crossfades through a gaussian blur
* (Desktop <-> Server / 桌面 <-> 服务器), three keywords, the install one-liner
* and stats. The rotating word is a stacked inline-grid so line width never jumps.
*/
import { Fragment, useEffect, useState } from "react";
import { Link } from "react-router";
@@ -57,16 +57,19 @@ export function Hero() {
<span className="h-1.5 w-1.5 rounded-full bg-brand-500" aria-hidden="true" />
{S.hero.badge}
</p>
{/* No text-balance: balance may break inside the breakable prefix ("…Agent /
Builder…"); greedy wrapping + the nowrap span pins the desktop break to
"Your Automated Agent Builder / Lives on Your Desktop". */}
<h1
className="anim-rise mx-auto mt-6 max-w-full text-3xl font-semibold tracking-tight text-balance sm:text-5xl"
className="anim-rise mx-auto mt-6 max-w-full text-3xl font-semibold tracking-tight sm:text-5xl"
style={{ animationDelay: "80ms" }}
>
{S.hero.titlePrefix}
<RotatingWord words={S.hero.titleWords} />
{S.hero.titleSuffix}
{S.hero.titleSuffixNoWrap && (
<span className="whitespace-nowrap">{S.hero.titleSuffixNoWrap}</span>
)}
<span className="whitespace-nowrap">
{S.hero.titleNoWrap}
<RotatingWord words={S.hero.titleWords} />
{S.hero.titleSuffix}
</span>
</h1>
<div
+105
View File
@@ -0,0 +1,105 @@
/**
* Deployment scenarios as an auto-cycling overlapped pair: the front card sits
* left at 92% width, the other slides right so its strip peeks past the front
* card's right edge, tucked underneath — on swap the two cross horizontally
* (both live in one grid cell, so the container takes the tallest card's height
* and nothing jumps). The front card holds for ROTATE_MS (long enough to actually
* read the paragraph); hovering the deck pauses the timer — nobody loses their
* place mid-read; clicking the peeking card (or a dot) switches manually. The
* covered card is a pointer-only shortcut and stays aria-hidden — dots carry the
* accessible switching — and prefers-reduced-motion disables auto-advance.
* SHOTS is index-aligned with S.scenarios.items; the photos are theme-agnostic,
* so a single asset serves light and dark (unlike the Cases screenshots).
*/
import { useEffect, useState } from "react";
import { S } from "../lib/strings";
import { Section } from "../components/section";
import medicalShot from "../assets/case-medical-qc.webp";
import lineShot from "../assets/case-line-inspection.webp";
const SHOTS = [medicalShot, lineShot];
const ROTATE_MS = 12000;
export function Scenarios() {
const items = S.scenarios.items;
const [active, setActive] = useState(0);
const [paused, setPaused] = useState(false);
useEffect(() => {
if (paused || items.length < 2) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const timer = setInterval(() => setActive((i) => (i + 1) % items.length), ROTATE_MS);
return () => clearInterval(timer);
}, [paused, items.length]);
return (
<Section
id="scenarios"
eyebrow={S.scenarios.eyebrow}
title={S.scenarios.title}
subtitle={S.scenarios.subtitle}
>
<div
className="mx-auto max-w-5xl"
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
>
<div className="grid">
{items.map((c, i) => {
const front = i === active;
return (
<figure
key={c.title}
aria-hidden={!front}
// The peeking card is itself the switch target (its visible strip is
// small, so the whole card surface accepts the click).
onClick={front ? undefined : () => setActive(i)}
// Cards are 92% wide in a shared cell: the front one holds the left
// edge; the covered one shifts its own 8.7% (= the container's 8%)
// so exactly that strip peeks past the front card's right edge.
className={`col-start-1 row-start-1 w-[92%] overflow-hidden rounded-2xl border bg-white transition-all duration-500 dark:bg-gray-900 ${
front
? "z-10 translate-x-0 border-gray-200 opacity-100 dark:border-gray-800"
: "z-0 translate-x-[8.7%] scale-[0.96] cursor-pointer border-gray-200/70 opacity-60 dark:border-gray-800/70"
}`}
>
<div className="md:flex md:min-h-[340px] md:items-stretch">
<img
src={SHOTS[i]}
alt={c.alt}
loading="lazy"
className="aspect-[16/9] w-full object-cover md:aspect-auto md:w-[48%]"
/>
<figcaption className="p-7 md:flex md:flex-1 md:flex-col md:justify-center md:p-12">
<h3 className="text-xl font-semibold sm:text-2xl">{c.title}</h3>
<p className="mt-4 text-[15px] leading-7 text-gray-600 dark:text-gray-400">
{c.body}
</p>
</figcaption>
</div>
</figure>
);
})}
</div>
{/* Manual switch doubles as the position indicator */}
<div className="mt-6 flex justify-center gap-2">
{items.map((c, i) => (
<button
key={c.title}
type="button"
aria-label={c.title}
aria-current={i === active}
onClick={() => setActive(i)}
className={`h-1.5 rounded-full transition-all duration-300 ${
i === active
? "w-6 bg-gray-800 dark:bg-gray-200"
: "w-3 bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-500"
}`}
/>
))}
</div>
</div>
</Section>
);
}