Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.0] - 2026-06-13

### Added
- **`consolidate` now works without an API key.** It picks a backend
automatically: the direct Haiku API when `ANTHROPIC_API_KEY` is set
(~1c/run), otherwise the local **`claude -p`** binary — your existing Claude
subscription login, **no API key needed** (post-2026-06-15 it bills as extra
usage and boots the environment per call, so it's pricier, but it requires no
key). With neither available it still skips cleanly, writing nothing.
`TJ_CONSOLIDATE_BACKEND=none` force-disables it.

### Internal
- `consolidate::summarize` (backend selection) + `consolidate_via_cli` reusing
the classifier's `run_claude_json` / `ClaudeBinaryStdinRunner` (recursion
guard intact).

## [0.19.0] - 2026-06-13

### Added
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ members = [
]

[workspace.package]
version = "0.19.0"
version = "0.20.0"
edition = "2021"
rust-version = "1.88"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion crates/tj-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ default = ["embed"]
embed = ["tj-core/embed"]

[dependencies]
tj-core = { package = "task-journal-core", version = "0.19.0", path = "../tj-core", default-features = false }
tj-core = { package = "task-journal-core", version = "0.20.0", path = "../tj-core", default-features = false }
anyhow = { workspace = true }
clap = { workspace = true }
tracing = { workspace = true }
Expand Down
18 changes: 10 additions & 8 deletions crates/tj-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3942,19 +3942,21 @@ fn run_consolidate(max_facts: usize) -> anyhow::Result<()> {
let texts: Vec<String> = sources.iter().map(|(_, t)| t.clone()).collect();
let source_ids: Vec<String> = sources.iter().map(|(id, _)| id.clone()).collect();

let consolidator = match tj_core::consolidate::Consolidator::from_env(max_facts) {
Ok(c) => c,
Err(e) => {
println!("skipped: {e}. Set ANTHROPIC_API_KEY to enable consolidation (~1c/run).");
let (backend, facts) = match tj_core::consolidate::summarize(&texts, max_facts)? {
Some(x) => x,
None => {
println!(
"skipped: no consolidation backend. Either set ANTHROPIC_API_KEY \
(direct Haiku API, ~1c/run) or install Claude Code so `claude` is on PATH \
(uses your subscription login, no API key needed)."
);
return Ok(());
}
};
eprintln!(
"consolidating {} high-signal event(s) via {} …",
texts.len(),
consolidator.model
"consolidating {} high-signal event(s) via {backend} …",
texts.len()
);
let facts = consolidator.consolidate(&texts)?;
if facts.is_empty() {
println!("no durable facts found");
return Ok(());
Expand Down
3 changes: 3 additions & 0 deletions crates/tj-cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5355,6 +5355,9 @@ fn consolidate_skips_without_api_key_and_spends_nothing() {
.current_dir(proj.path())
.env("XDG_DATA_HOME", xdg.path())
.env_remove("ANTHROPIC_API_KEY")
// Force the no-backend path so the test is deterministic even where
// `claude` is on PATH (which would otherwise be tried).
.env("TJ_CONSOLIDATE_BACKEND", "none")
.args(["consolidate"])
.assert()
.success()
Expand Down
63 changes: 55 additions & 8 deletions crates/tj-core/src/consolidate.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//! Memory consolidation (Pillar C): distil a project's recurring decisions and
//! constraints into a handful of durable semantic/procedural facts via a direct
//! Anthropic Haiku API call.
//! constraints into a handful of durable semantic/procedural facts with a
//! single LLM call.
//!
//! Direct API, not `claude -p`: post-2026-06-15 both bill as extra usage, but
//! `claude -p` also boots the whole user environment (~tens of k tokens) on
//! every call, while the direct API sends only our ~7k-token prompt — roughly
//! 1c per run versus 5-10c. This is a MANUAL command (one call per run, only
//! when the user asks), so it never resembles the per-prompt classifier burn.
//! No `ANTHROPIC_API_KEY` → the caller skips cleanly; we never fall back to a
//! Two backends, picked by [`summarize`]: the **direct Anthropic Haiku API**
//! when `ANTHROPIC_API_KEY` is set (cheapest — only our ~7k-token prompt,
//! ~1c/run), otherwise the local **`claude -p`** binary (subscription auth, no
//! API key needed, but it boots the whole environment per call so it's
//! pricier). With neither, the caller skips cleanly — we never fall back to a
//! heuristic, which would manufacture low-trust "facts".
//!
//! Either way this is a MANUAL command: one call per run, only when the user
//! asks, never wired to a hook — so it never resembles the per-prompt
//! classifier burn.

use anyhow::{anyhow, Context};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -91,6 +94,50 @@ impl Consolidator {
}
}

/// Run whichever summarisation backend is available and return its label plus
/// the facts it produced. Order: (1) `ANTHROPIC_API_KEY` set → direct Haiku API
/// (cheapest, ~1c/run); (2) else `claude` on PATH → local `claude -p`
/// (subscription auth, no API key, heavier per-call boot); (3) else `Ok(None)`,
/// so the caller skips with a message — never a heuristic.
/// `TJ_CONSOLIDATE_BACKEND=none` forces the no-backend path (disable / tests).
pub fn summarize(
events: &[String],
max_facts: usize,
) -> anyhow::Result<Option<(&'static str, Vec<ConsolidatedFact>)>> {
if std::env::var("TJ_CONSOLIDATE_BACKEND").as_deref() == Ok("none") {
return Ok(None);
}
if std::env::var("ANTHROPIC_API_KEY").is_ok() {
let c = Consolidator::from_env(max_facts)?;
return Ok(Some(("haiku-api", c.consolidate(events)?)));
}
if crate::classifier::agent_sdk::claude_on_path() {
return Ok(Some(("claude -p", consolidate_via_cli(events, max_facts)?)));
}
Ok(None)
}

/// Summarise via the local `claude -p` binary (subscription auth). Reuses the
/// classifier's command plumbing — including the recursion guard set by
/// `base_claude_command` — and unwraps the `--output-format json` envelope.
fn consolidate_via_cli(
events: &[String],
max_facts: usize,
) -> anyhow::Result<Vec<ConsolidatedFact>> {
if events.is_empty() {
return Ok(Vec::new());
}
let prompt = build_prompt(events, max_facts);
let model = std::env::var("TJ_CONSOLIDATE_MODEL")
.unwrap_or_else(|_| crate::classifier::agent_sdk::DEFAULT_MODEL.to_string());
let text = crate::classifier::agent_sdk::run_claude_json(
&crate::classifier::agent_sdk::ClaudeBinaryStdinRunner,
&model,
&prompt,
)?;
Ok(parse_facts(&text))
}

/// The summarisation prompt. Deliberately strict: durable-only, fixed line
/// format, "output nothing" escape hatch so the model doesn't pad.
pub fn build_prompt(events: &[String], max_facts: usize) -> String {
Expand Down
2 changes: 1 addition & 1 deletion crates/tj-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ path = "src/main.rs"

[dependencies]
# Lean: the MCP server doesn't embed yet, so it skips the model2vec backend.
tj-core = { package = "task-journal-core", version = "0.19.0", path = "../tj-core", default-features = false }
tj-core = { package = "task-journal-core", version = "0.20.0", path = "../tj-core", default-features = false }
anyhow = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "task-journal",
"version": "0.19.0",
"version": "0.20.0",
"description": "Append-only journal of AI-coding task reasoning chains: hypotheses, decisions, rejections, evidence. Renders compact resume packs so an agent can pick up a 2-week-old task with full context.",
"author": {
"name": "Mher Shahinyan"
Expand Down
Loading