git clone https://github.com/theohmwoa/forge && cd forge
cargo build
cargo test --all
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --checkRust 1.75+ (workspace pins via rust-toolchain.toml). No nightly features.
cargo install --path crates/forge-cli --force
forge --helpThe binary lands in ~/.cargo/bin/forge. Make sure that's in your PATH.
crates/
├── forge-anthropic # Anthropic Messages API adapter
├── forge-cli # the `forge` binary (this is what cargo install builds)
├── forge-core # step types + Agent / Tool / Matcher traits
├── forge-gemini # Google Gemini generateContent adapter
├── forge-openai # OpenAI Chat Completions adapter
├── forge-recorder # axum proxy: records LLM calls into Forge
├── forge-rig # Record a Rig conversation into the Forge graph
└── forge-storage # Storage trait + Memory / Sled / Postgres backends
- New crate
forge-<provider>mirroringforge-anthropic. - Implement the
Agenttrait (inforge-core):#[async_trait] impl Agent for MyProviderAgent { async fn next_step(&mut self, parent: Option<NodeHash>) -> Option<StepKind>; }
- Provide both
MyProviderAgent::new(config, prompt)for fresh runs andMyProviderAgent::continuing(config, prefix)that translates a&[Step]prefix into the provider's wire format. - Wire into
forge-cli/src/main.rs: add anAgentKindvariant and match arm inbuild_fresh_agent/build_continuing_agent.
In forge-core::tool or your own crate:
pub struct MyTool;
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str { "mytool" }
fn description(&self) -> &str { "..." }
fn schema(&self) -> serde_json::Value { /* JSON Schema */ }
async fn run(&self, input: &serde_json::Value) -> anyhow::Result<serde_json::Value> {
// ...
}
}Add a ToolKind variant in forge-cli/src/main.rs.
Implement the Storage trait in forge-storage:
#[async_trait]
impl Storage for MyStorage {
async fn put(&self, step: Step) -> anyhow::Result<NodeHash>;
async fn get(&self, hash: &NodeHash) -> anyhow::Result<Option<Step>>;
async fn children(&self, hash: &NodeHash) -> anyhow::Result<Vec<NodeHash>>;
async fn record_run(&self, meta: &RunMeta) -> anyhow::Result<()>;
async fn run_meta(&self, head: &NodeHash) -> anyhow::Result<Option<RunMeta>>;
async fn list_runs(&self) -> anyhow::Result<Vec<RunMeta>>;
// chain_to has a default impl walking parents via get; override if
// your backend can do it in one query (Postgres uses a recursive CTE).
}Wire into forge-cli/src/main.rs::open_storage so the CLI auto-detects
your backend from the --db URL prefix.
cargo fmt --allbefore committing (CI checks this).cargo clippy --all-targets -- -D warningsclean —#[allow(...)]only with a comment explaining why.- No new
unwrapin non-test code; useanyhow::bail!or?. - Tests live in
#[cfg(test)] mod testsnext to the code. - Postgres tests gated on
TEST_POSTGRES_URLso CI without a DB still passes.
Imperative subject, <60 chars. Body explains the why, references
issues only when relevant. No emoji.
Not yet — pre-1.0. Bump version in Cargo.toml's [workspace.package]
when we get there.