Skip to content

Releases: BackTrackCo/tenjin-agent

v0.1.0-alpha.19

v0.1.0-alpha.19 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 26 Sep 00:22
1a81027

Minor Changes

  • ddf34f7: Separate automatic router limits from mandatory manual payment consent, check Base USDC balance before signing, repair Ultravioleta discovery, and retire the obsolete payment skill.

    maxAutoSpend and sessionBudget limit automatic router purchases only. Zero blocks positive automatic spending; daily none removes the automatic daily ceiling. A missing daily limit defaults to $5 both before and after install. Manual tenjin pay ignores both configured limits and always requires consent for the quoted transaction, interactively or through --yes after explicit user approval. It is never an autonomous workaround for a router refusal. An optional --max-price, creator/destination restrictions, supported payment terms and the balance check still apply.

    Legacy bazaarPay and confirm keys are ignored after upgrade, including old false/always values. Doctor/status warn; install and refresh remove and report them while preserving current router/limit settings and unrelated fields. config set rejects the retired keys. No hand edit is required to keep routing operational.

    Manual and automatic payments share one ledger and duplicate guard. Manual payments remain in total reporting without consuming automatic budget headroom. Legacy exposure without mode/counter metadata conservatively counts as automatic until the existing window expires.

    Missing Bazaar metadata or an unlisted endpoint is an ordinary direct-payment outcome. Only unavailable/incomplete lookups or exact-listing term differences require invocation-scoped --ignore-warnings; --yes supplies payment consent only. The selected signer's balance is read before signing, with one bounded retry on an unreadable result. Confirmed insufficient funds or persistent read failure refuses and releases the reservation. Router calls retain their advertised-price checks and wire protocol; policy refusals now say “Blocked by spending policy.”

    Do not roll back to a config reader that treats zero as unlimited or ignores none; preserve the operator's explicit automatic limits when reverting behavior.

Patch Changes

  • 1f6100f: The pre-call router hook no longer blocks a WebSearch for the free library docs
    lookup: the search runs as written, Context7 docs for the same query are fetched
    alongside it, and when found they are added above the search's own results
    (nothing changes when none are found). Paid lookups are redirected exactly as
    before, and tenjin install --refresh gives the after-call hook the 15 second
    timeout that wait needs. A provider's own error message, such as the docs
    lookup's "no library matched" 404, now reaches the agent from
    mcp__x402__request instead of a bare status telling it to retry.
  • de8c3c0: The pre-call router hook no longer redirects WebSearch or WebFetch twice in a
    row to the same kind of lookup. When the mcp__x402__request lookup it
    redirected to does not come back fulfilled (it failed, stopped to ask, or was
    never called), or was a free lookup, whose success nothing verifies, that agent's next call matched to the same kind runs as it is,
    while other lookups, and the call after that, are routed as usual; the main
    agent and each subagent keep their own record. The redirect's reason tells the
    agent it can make its own call again if the lookup does not cover it.
  • 2f41cb2: Keep tenjin update refreshes at user scope when they run from the home directory, avoiding an unintended project-scoped x402 MCP registration. Recognize symlinked home paths, preserve existing project-only registrations and explicit project installs, and document cleanup for the accidental alpha.18 entry.

v0.1.0-alpha.18

v0.1.0-alpha.18 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 25 Sep 01:28
4c1a253

Patch Changes

  • df153c2: Eight fixes to paid lookups. A paid body is now always delivered: one that fails
    the decision's success rule comes back unverified with a caveat naming the
    rule it missed, where it used to be refused after the money had moved. The
    request tool now sends its result once instead of twice, and declares
    anthropic/maxResultSizeChars of 200,000 so Claude Code keeps a full page read
    inline; a larger result is saved by Claude Code itself and the model is handed
    the path. A 633,016-character page read used to go past the tool-output limit,
    so the model never saw what it paid for. The pre-call hook now denies the main
    agent's WebFetch or WebSearch only when the paid lookup would run without
    approval; over the cap or past the budget, the free call runs and the
    after-call offer still applies, where a deny used to leave the page neither
    fetched nor bought. The prompt hook follows the same rule: its hint to call
    request is shown only when that call would run without approval, where it
    used to send the model to a needs_approval stop while its free tools would
    have answered. Both, and a subagent's offer, also need the wallet to cover the
    price: once the policy allows the spend, the hook reads the wallet's USDC
    balance with one balanceOf against rpcUrl, inside the hook's existing time
    budget, and a balance below the price leaves the free call to run. A fresh
    install's empty wallet used to get its WebFetch denied and then a refused
    payment. A balance that cannot be read leaves the policy to decide, as before.
    tenjin pay waits on the paid request for the seller's
    advertised maxTimeoutSeconds, capped at 120 s and never shorter than
    --timeout, instead of cutting off a signed payment at the 10 s default; the
    unpaid probe keeps --timeout. The Bazaar lane now looks a resource up
    itself: it asks each registry's /discovery/search for this URL under the live
    payTo, and falls back to the payTo-filtered list where a registry has no
    such search. CDP's list ignores payTo and returns the same first page of
    about 17,000 listings, so any endpoint no discover sweep had stored was
    refused as unlisted. And the lane's refusals and the tenjin-pay skill no longer
    name the shelved tenjin discover.
  • 54e64c5: A router packet no longer carries rows the harness writes into the user's turn. A
    background task or subagent finishing (origin.kind: "task-notification") and a local
    command's output (<local-command-stdout>, <local-command-stderr>) arrive as
    type: "user" rows without isMeta, so they were read as the user's words: they travelled
    in history, and one could become current, even with router.context turn. They are now
    skipped like harness meta rows. A <command-name> row, the command the user typed, stays.
  • 0e7a557: tenjin uninstall leaves an x402 MCP server it did not write. Before removing the
    registration it reads the scope's own file the way install does, and when the x402
    entry launches something other than tenjin mcp, or the file cannot be read, it removes
    nothing and says why instead of running claude mcp remove x402. An entry that is the
    router's, or no entry at all, is removed as before.

v0.1.0-alpha.17

v0.1.0-alpha.17 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 24 Sep 04:46
a1db764

Patch Changes

  • 2a95507: tenjin hook <name> for a hook arm this binary does not know now exits 0 with
    nothing on stdout, the same "no opinion" every handler gives on a bad event.
    It used to exit 2 as a usage error, which Claude Code reads as a blocking hook
    failure: a settings file written by a newer tenjin install (a new arm, or a
    source build ahead of the npm release) then failed every call of the matched
    tool until the binary caught up.

  • 46fc65f: The router has an off switch and a context setting that every router hook and the
    request tool honour. tenjin config set router.enabled false stops it on this
    machine, and tenjin config set --project router.enabled false stops it in one
    repository through a committed .tenjin/config.json; --project --local writes
    a personal .tenjin/config.local.json beside it instead, which belongs in
    .gitignore. Off means the hooks send nothing and the request tool answers
    needs_input naming the key, paying nothing. router.context turn sends the
    current turn with no prior messages. Every project file from the working
    directory up to the git root applies (in a git worktree, the main checkout's
    too), and each can only turn the router off or narrow what it sends. tenjin config shows
    both keys with the file they came from, and tenjin doctor warns when the hooks
    are wired but the router is off in the directory it runs from.

  • 0be992c: Router hooks are now safe inside subagents. WebSearch and WebFetch are routed
    before each call as before, and a fitting paid lookup still redirects the call
    to mcp__x402__request; but a subagent is redirected only when it is known to
    have mcp__x402__request (its own tools:, or the built-in general-purpose,
    Explore and Plan agents) and your spend policy would pay without asking, and
    it is routed on its own task rather than the parent's last message. When a free
    call clearly fails (blocked, a server error, an empty page, a search with no
    links, a network error; never a 404 or 410), the router is asked once and may
    offer a paid lookup. A new PreToolUse hook on Agent|Task appends a fitting
    offer to the task a subagent is handed. Every line the router adds opens with
    Tenjin router (installed by the user): and names the call
    mcp__x402__request. tenjin doctor names custom agents whose tools: leave
    the paid tool out. Existing installs keep working as they are;
    tenjin install --refresh (which tenjin update runs) adds the new hooks.

  • dea376f: Every router packet is masked before it leaves: the prompt, the prior messages,
    the literal URLs and the pending search or URL go through one seal step that
    applies the publish scan's key, PEM, BIP-39 seed-phrase and URL credential rules
    (a credential query parameter, a long token-shaped path segment). Harness meta
    rows no longer travel. A native WebSearch or WebFetch whose search or URL
    carries a credential, or whose URL is local or private, now runs natively with
    no router call. The mask also covers a quoted "password": "..." value,
    Authorization: Basic, curl -u user:pass, a 40-hex node key in a URL path, a
    64-hex key without 0x (whole or split 32+32), a Solana secret key (base58 or
    the keygen byte array), and a checksum-valid recovery phrase in any case, with
    commas, quotes or line breaks between its words.

    The request tool refuses a live 402 above the price the routing decision
    quoted, on every pay lane, before anything is signed. A provider or a stale catalog can no longer
    charge more than it advertised; maxAutoSpend and sessionBudget still cap
    every payment.

v0.1.0-alpha.16

v0.1.0-alpha.16 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 23 Sep 05:36
38849ef

Minor Changes

  • 720f868: Grade a finding against the transcript it actually landed in, including a
    subagent's own.

    Every arm now records the subagent it fired inside. The harness stamps
    agent_id on a hook input that fires inside a subagent and leaves it off in the
    main session, while session_id stays the parent's either way — so until now a
    row written inside a child pointed only at a parent transcript that holds no
    word of what the child did. events, injections, searches and
    pairing_closes all gain an agent_id column in one step, and the prompt,
    failure, pass, edit, research, dispatch and subagent arms all stamp it. The
    subagent arm records the child the finding was relayed TO, which is the file the
    verdict has to come out of; the close on a pairing records the worker that made
    it, and counts for nothing — the promotion to verified still asks for two
    independent SESSIONS, because two subagents of one conversation are one laptop
    in one checkout.

    Schema version 2, and tenjin-agent#247 is what it is. Anything after this
    takes version 3. A fresh file is CREATED at the current shape and steps nothing;
    a file that already exists is altered in place by the first open of either the
    CLI or a hook, keeping every row. The step also BACKFILLS events.agent_id from
    the data.agentId that tenjin-agent#242 has been writing since 2026-08-28, so
    the fortnight of rows in between are still the worker's rather than the lead's.
    ALTER TABLE ADD COLUMN is not idempotent, so the version is re-read inside the
    BEGIN IMMEDIATE and the create and migrate branches are exclusive — pinned by
    a dozen hook processes racing one version 1 file, and by a version 1 hook core
    writing to a version 2 file, which is the ordinary state of a machine that
    upgraded the CLI without re-running tenjin install.

    One identity, parsed once. The prelude has a single reader, identityOf,
    answering the session and the agent together; an id that is not [A-Za-z0-9_-]
    of 1–128 characters is refused rather than stripped, because it is also a
    transcript filename and stripping a separator out of one id spells another id
    exactly. NULL is the main session everywhere and never "unknown", with the one
    place it becomes the '' a session_state key segment needs spelled out as
    agentKey — so edited::<path>, edits::<path> and replayed::<head> are
    byte-for-byte the keys the lead's rows already sit under. The importance score
    reads the events column instead of a JSON field, which is what makes "this
    child was shown a finding" and "this child then fixed something" the same worker
    rather than two.

    An id the harness stamps but this build cannot use is not the lead. It fails
    the bound, so it names no transcript and no partition; filing the fire under the
    main session instead would hand a child's search, edit or close to its parent,
    and the score would then read that work as the lead's. Every arm drops such a
    fire whole — no lookup, no event row, no decision row — rather than inventing a
    reason bucket for a row that should not exist.

    tenjin grade reads that file. A row with an agent id is judged against
    <session>/subagents/agent-<id>.jsonl, never the parent's. A relayed finding has
    no anchor row in any transcript — the child is handed it as its opening context
    and nothing records it — so it is judged from the child's first tool call onward,
    by the same evidence rules everything else gets. It also leaves no injected text
    on disk, so its span evidence comes from the piece's title alone, which usually
    means a relayed finding is judged on the strong evidence (an explicit read, or
    the URL) or not at all.

    unobserved narrows to what it always meant: nothing to read and nothing
    that ever will be. Every subagent injection used to land there unconditionally,
    which closed the whole handoff as never-seen; now only a relayed row with no
    agent id recorded does — rows written before this version, or by an arm that
    could read none off its input. --explain names the agent and the file that
    answered.

  • 79b8862: Codex runs the same loop. tenjin install on a machine with Codex writes seven command entries to ~/.codex/hooks.json (or $CODEX_HOME/hooks.json), all through the shim, beside the Claude Code set; doctor reports them as configured and observed in the ledger; uninstall removes only the entries it wrote. The daemon serves /hook/codex with an adapter built on payloads captured from codex-cli 0.153.4: root prompt lookups, per-child identity through agent_id, multi-file apply_patch edits marked in one fire, and the capture ask delivered as a Stop decision: block.

    Shared boundary corrections that a second harness forced: the tool an arm reads is canonical (command, paths, task, query, url) rather than a vendor argument bag; a stored session is namespaced by harness (claude:<id>, codex:<id>), so loop.db state from before this release does not join new sessions; tenjin search stamps the thread it ran inside (CODEX_THREAD_ID) as agent_id, and the capture ask names a CLI miss only to the actor that opened it. A Codex shell result carries no exit status, so its completion stays unknown (an error marker is still a failure) and no pairing closes on it; the spawn tool's task is opaque on the wire, so no dispatch handoff runs for Codex.

  • 00b382a: Take a publish back from the CLI. tenjin delete <postId> removes one of your own
    pieces through the owner-scoped soft-delete at DELETE /api/posts/<id>, and
    tenjin edit <postId> --status draft|published is the reversible half: draft
    unpublishes without losing the id or the body, published puts a draft up. Both
    reuse edit's signing path, and the MCP server exposes them as tenjin_delete
    (annotated destructive) and a status argument on tenjin_edit.

    delete confirms in every mode, and never reads publish.mode. The mode is
    consent to publish, not consent to destroy, so full-auto asks here exactly as
    review does. At a terminal it asks y/N inline; anywhere else, including under
    an agent or the MCP server, it refuses with NEEDS_CONFIRMATION (exit 3) carrying
    the title, status, url, the --yes command that confirms, and the reversible
    --status draft alternative, which is the same exit-3 channel publish and edit
    already use. --status is an ordinary change flag by contrast: it diffs, it prunes
    when it already matches, and it rides the ordinary publish.mode gate.

    Every successful tenjin publish now prints the exact undo commands with the real
    post id, and carries them on the --json envelope as data.undo, so an agent
    reporting a publish hands over a real command instead of guessing one. The printed
    removal command carries no --yes: it starts the undo, so each surface's own
    confirmation still runs, and --yes appears only in a refusal payload's
    confirmCommand, which answers a question the user has already been shown.

    A server refusal after the confirmation is the new DELETE_FAILED (exit 4), whose
    message says the piece is still live. No refused delete leaves a write credential
    behind: the owner-scoped read that the preview is built from signs with a
    read-scoped session, and only an actual approval mints read+write, so both a
    headless refusal and a declined prompt end with nothing on disk that a later write
    could reuse. An approved prompt pays one extra in-memory signature for that, with
    no second keystore prompt and no extra round trip. tenjin delete is never
    allowlisted: no mode carries it, and it is not delegable to a subagent.

    Promoting a draft settles what the draft publish deferred. edit --status published re-scans the stored body at the block tier (a draft made on the web
    desk was never scanned locally), claims the searches a
    publish --draft --search-id parked on the draft (the ids ride the same PUT and
    their local loops close), and writes the same-body dedup marker, so the next
    publish of the promoted body dedups instead of creating a duplicate. The
    server-returned post id is shape-checked as a uuid before it can reach the undo
    line or the delete confirm payload.

  • 9656c04: The failure arm fires behind a fixed allowlist of command heads; every search names its arm; publish --key (tenjin-agent#212, PR A).

    The allowlist is fixed. git is no longer a head the arm fires behind: every record it opened had come from git show … | grep ENOENT over source that merely mentions an errno. node, deno, python and python3 count only when their first argument is a file or their own test runner (node --test, deno test), so python3 -c, node -e and a piped stdin never do; python3 -m pytest and python -m unittest are read as pytest and unittest, the module being the program.

    tenjin search names itself too. The hook arms already send trigger on each /api/search body; this release adds the manual half, so tenjin search and the MCP search tool over it send cli explicitly rather than relying on the server's default. The arms that send one are research, dispatch, prompt and failure. Telemetry only; a shelf that predates the field records cli.

    tenjin publish --key <kind=value> (repeatable, up to 32; the MCP publish tool's key) sends exact-match keys on the post body — fingerprint, package_version, command_head, repo — bounded before the wallet signs. Keys go out unverified; verified is the shelf's own claim about a key. A shelf with KNOWLEDGE_KEYS off refuses a keyed body as ...

Read more

Bench-1 vendored toolchain: vitest 3.2.4 (node24, darwin-arm64)

Choose a tag to compare

@A1igator A1igator released this 10 Sep 19:51
c9becc4

Not a product release. This tag exists only to host one build artifact for the
Bench-1 benchmark harness (evals/benchmark/), so that the artifact does not
live in git history.

vitest-3.2.4-node24-darwin-arm64.tar.gz is the deterministic pnpm install of
the Bench-1 live task fixture's pinned lockfile: 781 files, darwin-arm64
natives, node ABI 137, vitest 3.2.4, pnpm 11.11.0. Every live-task
fixture_hash in the benchmark manifests folds this archive's digest in, so
these exact bytes are the pin. A rebuild produces different bytes and re-pins
every manifest.

The committed record beside the fixture,
evals/benchmark/fixtures/live/vendor/vitest-3.2.4-node24-darwin-arm64.json,
states the digest:

sha256:a1c55230908e6ad737ac20f06381c23fdc6a2f080488809992dad751ae5317b3

evals/benchmark/vendor.py uses the local file when it is there and fetches
this asset when it is not, verifying that digest before anything reads the
bytes. A mismatch is a refusal, never a warning.

Rebuild recipe: evals/benchmark/scripts/vendor-vitest.sh.

v0.1.0-alpha.15

v0.1.0-alpha.15 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 25 Aug 01:16
b2117a6

Minor Changes

  • 18385e4: Add a working native Hermes Agent integration. tenjin install --harness hermes
    now installs the Tenjin skills, adds a conservative MCP entry, and enables a
    stdlib-only Hermes plugin that checks Tenjin before web_search, attaches a hit
    to that tool's result, and surfaces unresolved searches through
    transform_llm_output for publish-back.

    Hermes reuses the same generated, bounded, fail-open retrieval/store/nag scripts
    as Claude Code instead of carrying a second payment-facing implementation. The
    installer honors an absolute HERMES_HOME, embeds absolute executable paths,
    preserves unsupported or user-owned YAML byte-for-byte, never overrides
    plugins.disabled, keeps automatic detection inert until explicit activation,
    and adds a warn-level doctor check. It adds no TENJIN_HARNESS policy selector
    and does not copy or couple wallet state.

    Hook consent and plugin activation are two separate decisions. --no-hooks and
    --search-hooks off withhold the Hermes scripts, plugin, and activation exactly
    as they withhold Claude's settings.json entries; the mcp_servers.tenjin entry
    is a server registration, so it is still written. Where the stored
    hooks.searchMode is what holds the plugin back, install and tenjin doctor say
    tenjin config set hooks.searchMode auto rather than an install command that
    cannot move the blocker. Withholding a write is not an uninstall, so install
    reports it as skipped and names any enabled plugin an earlier run left behind.

    Re-pointing the MCP entry (an nvm switch, a pnpm-vs-npm global) rewrites the
    managed block in place, leaving one marker comment and any neighbouring comments
    untouched. tenjin doctor tolerates a relative HERMES_HOME set for some other
    tool, reports a baked MCP command that no longer exists as stale rather than
    green, and shares the installer's classifier so its fix cannot point into a
    conflict it did not predict.

  • ba09366: Close a batch of open search loops in one call, and let the WebSearch hook say
    which requests are its own.

    The hook now leads its User-Agent with tenjin-websearch-hook/<version>
    instead of the CLI product, so a query that rode along with a web search is
    separable from a question an agent deliberately looked up. Tenjin attributes a
    request to the first User-Agent product, so that position is the whole
    mechanism: it lands as client_name on the search row, and the marketplace's
    /trending questions tier filters on that exact name rather than showing readers
    queries nobody vetted. A deliberate tenjin search is unchanged and still
    identifies as tenjin-cli. The caller handoff still composes behind whichever
    product leads. tenjin install rewrites the hook scripts.

    tenjin outcome --search-id now repeats, reporting one status against every id
    named, and --all-open closes this session's open hook loops. Both report per
    id in the JSON envelope, and both refuse the whole batch before sending anything
    if one target's id or status could not be right. A batch that closed some and
    failed others says exactly which.

    The sweep is per session because the loop is: a session's open loops are its
    own, and one that ends leaves its unpublished debt to decay rather than handing
    it to whichever session stops next. The session comes from TENJIN_SESSION_ID,
    then CLAUDE_CODE_SESSION_ID, the same resolver search stamps entries with,
    and an entry no harness could attribute stays in scope everywhere rather than
    nowhere. There is no machine-wide sweep.

    --all-open reports regenerated and nothing else, and sweeps MISSes only. The
    other statuses are claims about what a specific search did for the agent, and a
    blanket used over queries nobody examined one by one is attribution the
    marketplace would be right to trust and wrong to believe. A hook search Tenjin
    answered is left alone for the same reason one step further in: that is the
    search where a piece may have been bought and read, and regenerated would
    overwrite the only positive attribution the loop collects. Deliberate searches
    are never swept. Both kinds are counted and named in the output, so a blanket
    close cannot look more complete than it is.

    A batch stops at the first rate limit or transport failure and reports the rest
    untouched, rather than spending an agent's outcome budget on requests that will
    fail the same way: an open loop is the safe state, and the Stop hook raises it
    again.

    The Stop hook's end-of-turn reminder now names ONE close command for the whole
    batch of hook misses instead of a per-id one. Seventeen open loops meant
    seventeen outcome calls, which is enough friction that the honest close stops
    happening at all.

  • 01daf8b: Search now speaks schemaVersion 3 to POST /api/search with
    view: "decision". POST /api/agent/search is a deprecated alias that answers
    410 after one deprecation window (BackTrackCo/tenjin#137), so nothing in the CLI
    still calls it: not tenjin search, not the MCP tenjin_search tool, and not
    the WebSearch/dispatch hook scripts tenjin install writes.

    The v3 request is not the v2 request with a new path. question becomes the
    documented query, view is named explicitly rather than left to the server
    default, and freshWithin/maxPrice/appliesTo move under a nested filters
    object. That nesting matters: the v3 route STRIPS an unknown top-level key into
    a non-fatal warnings array instead of rejecting it, so a top-level maxPrice
    would have run the search unfiltered with no error anywhere.

    The response envelope changed with it. decision + candidates[] became
    matched + items[], and the MISS browse[] tail is gone — the decision view
    draws no fallback shelf, so a miss is an empty result plus a hint pointing at
    GET /api/articles. --json carries the server's envelope verbatim, so an
    agent parsing decision or candidates must move to matched and items. The
    local search store keeps its CANDIDATES/MISS vocabulary, because tenjin outcome branches on it and older entries carry it; it is derived from whether
    anything matched rather than read off a field that no longer exists.

    tenjin doctor's search-contract probe looks for /api/search in the
    deployment's OpenAPI paths. A deploy advertising only the alias now warns, which
    is the case the probe exists for.

  • e91f3eb: Two new harness hooks, so an agent looks before it researches rather than after.

    A SessionStart primer. install now writes ~/.tenjin/hooks/tenjin-sessionstart.mjs
    and registers it for startup, clear and compact. It prints one paragraph:
    what Tenjin is, the entry gate (public, durable, costly to reproduce), the
    instruction to state the question in one line and search first, the reminder to
    name Tenjin when enumerating research sources for a subagent, and what to skip.
    It makes no network call and reads no state. Nothing else may join it — no update
    line, no publish nudge, no open-loop summary — because the measured bottleneck is
    the retrieval reflex, and a paragraph that grows stops being read. tenjin config set hooks.sessionPrimer off silences it at run time with no re-install.

    A research-dispatch hook. ~/.tenjin/hooks/tenjin-dispatch.mjs fires on
    PreToolUse for Agent|Task, the two names one subagent dispatch goes by across
    Claude Code versions. The WebSearch hook only ever saw a question the agent had
    already decided to ask the web; the expensive research is the work it delegates to
    a subagent, and this rides along with that. It sends the dispatch's description
    plus at most 400 characters of its prompt — a privacy bound, not a display one —
    and on a CANDIDATES decision mentions at most two tested answers in the WebSearch
    hook's own format, in the parent's context only, since the tool input is already
    formed by then. It shares that hook's whole boundary: no permissionDecision, a
    2s fetch budget under the harness's 5s kill, and a silent exit 0 on every failure.
    hooks.searchMode governs it, so off and remind behave as they do for web
    searches, and the disclosure at install time now names the subagent prompt and its
    400-character bound.

    Two bounds on a fan-out. The same question is asked once per session, because
    a fan-out dispatches near-identical prompts and the answer is already in the
    store, and a session gets at most 10 dispatch lookups however wide it fans out, so
    a ten-way research turn cannot put the fetch budget in front of every subagent.
    Nothing fires on a WebFetch.

    Dispatches record into the CLI's own searches.json under a new dispatch-hook
    source, so a HIT still attributes a later purchase and buy <resourceId> still
    resolves the read URL. They are never nag material: the Stop hook's strong arm
    stays cli-only and its weak arm stays websearch-hook-only, an entry from any
    other source is skipped unnagged rather than promoted, and outcome --last skips
    them for the same reason it already skipped web-search entries. Because nothing
    ever closes one, they also hold at most 15 of the store's 50 slots: a demand entry
    is telemetry, and the store's other two jobs are resolving a payable read URL and
    finding the last deliberate search, so a wide fan-out must not be able to drain
    either.

    install discloses both hooks and what leaves the machine, uninstall removes
    both scripts and both entries, and the wiring stays idempotent: each script owns...

Read more

v0.1.0-alpha.14

v0.1.0-alpha.14 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 17 Aug 03:53
0c92f5b

Minor Changes

  • 6b960e1: Let the agent that runs the CLI travel in the same User-Agent field, behind the
    tenjin-cli product: export TENJIN_CALLER_USER_AGENT when you launch it.

    Composition happens in one place (composeUserAgent in lib/client-meta.ts) and
    is idempotent: the caller value is decomposed into products, any copy of our own
    product or comment is dropped from it, and the field is rebuilt from the package
    identity. A retry, a nested helper, or an agent re-exporting the env it received
    therefore cannot mint a second tenjin-cli token, and the caller's own products
    survive byte for byte in their original order. The write still runs through the
    Headers API, so a call-specific User-Agent in any casing cannot erase the
    composed field or add a second one.

    The handoff accepts a product sequence and nothing else, which is what keeps a
    user, wallet, session, hostname, or machine identifier structurally out of it. A
    value that is not printable ASCII, not a bare product sequence, or long enough to
    push the composed field past the 512 characters the server accepts is omitted
    whole: the CLI identity travels alone rather than as a truncated token that would
    read as a different client. It is self-reported telemetry, never trusted policy
    input, and no signed header set changes: the payment and RFC 9421 signatures
    cover the same material they did before.

    The generated WebSearch hook script carries the identity too. It is the CLI's
    highest-volume request path and it imports nothing, so it had been sending Node's
    default User-Agent: node and every hook-driven search was landing as a
    synthetic client named node that was in fact this CLI. It now composes the same
    field, from constants interpolated out of lib/client-meta.ts at generation time
    and the caller handoff read at run time, with a test that runs the shipped bytes
    and the real composer over the same inputs so the two cannot drift. Because the
    version is baked when the script is written, an installed hook keeps reporting
    the version that wrote it until tenjin install is re-run: tenjin update
    replaces the binary and leaves hook scripts alone.

  • 77244fd: Publish-back loop DX (tenjin-agent#161, #162), plus a skill diet.

    The Stop hook's weak arm now fires at most once per session. A research fan-out
    mints new searchIds every turn, so per-searchId dedupe never rate-limited it and
    the batch read as harness debug output. hooks.stopNag gains deliberate-only,
    which drops that arm and keeps the deliberate-search one, so silencing the noise
    is no longer the cliff off is. Whatever the hook raises now leads with the
    resolved publish.mode, which an agent otherwise had to run tenjin config get
    mid-publish to discover. That line resolves the mode in the CLI's own order,
    global config then a project .tenjin.json found by walking up from the session's
    cwd then TENJIN_PUBLISH_MODE, so the hook, publish, and doctor agree on what
    the next publish in that directory will actually run under. That walk stops where
    the CLI's stops: at the repo root, never above $HOME, and skipping a
    .tenjin.json owned by another user. A project full-auto conservatively reads
    as auto.

    tenjin publish --search-id <id> re-links a loop something else already closed,
    so a MISS closed as regenerated while the answer was still being written can
    still be attributed to the piece that answered it. A repeat publish against a
    loop an earlier publish already closed now says so instead of reporting a fresh
    close it did not make.

    tenjin publish --search-id <id> now sends that id to the server, which accepts
    and documents it as the attribution for the MISS the piece answers. It never
    reached the wire before, so every publish that named a search still went out
    unlinked, the thing #161 is actually about. The id is validated at the command
    edge against the shape the server declares rather than the CLI's looser uuid, so
    a bad one costs a message instead of a 400 collected after the wallet signature.
    A --draft publish sends no attribution, matching the local ledger, which has
    always treated a draft as answering nobody.

    Installing Tenjin is the consent for auto-publishing

    Every install settles publish.mode at auto unless told otherwise, and the
    first install, headless included, writes the two mode-gated rules
    Bash(tenjin publish:*) and Bash(tenjin edit:*) alongside the nine free ones.
    The terminal says it in plain words, two lines and a link: what the agent will now
    do, how many rules are allowed, and the one command that turns it off. The
    complete disclosure (both rule strings, unattended keystore access, the read+write
    session credential minted on disk, publishing the contents of any local file the
    agent can read, the --base-url and --yes caveats, all three undos) is
    unchanged in docs/agent-permissions.md, in tenjin doctor --json, and in this
    command's own --json envelope.
    edit travels with publish because it runs the identical consent gate, touches
    only posts the wallet already owns, spends nothing, and creates no new public
    content; an auto mode that can publish a post unattended but cannot fix that
    post's price is the asymmetry the mode exists to remove.

    tenjin config set publish.mode syncs the pair at the moment the mode changes.
    Loosening to auto or full-auto asks once at a real terminal, naming both rules,
    and writes on yes; no terminal, --json, or a decline writes nothing and points
    at tenjin install. Tightening back to review retracts both unprompted through
    a retraction-only pass that never appends, so it retracts on any machine
    carrying them, including one whose free tier no longer matches what this release
    ships. uninstall reclaims both. The bare CLI, with no install ever run, still
    defaults to review.

    tenjin install and tenjin config set now preserve ~/.claude/settings.json's
    file mode, so a chmod 600 on a file holding an env block survives a write.

    Every install line that reports the allowlist says what a review run took back,
    and names the file it took it from. The retraction runs above the guards that
    decline a write, so a run can retract and then skip, and two skip lines described
    the file as untouched: "unchanged" under --no-allow-free-verbs, and "not wired
    (Claude Code only)" under another --harness, which read as "your Claude settings
    were left alone" on the run that had just deleted two rules from them.

    Moving to review retracts on every install path. The retraction sat below the
    --no-allow-free-verbs and --harness guards, so a run that declined the free-verb
    WRITE also silently declined the REVOCATION: install --publish-mode review --no-allow-free-verbs wrote mode: review and left both rules allowed. It also
    returned before the additive pass, so one review-install retracted the pair,
    claimed a free tier it had not written, and stranded a legacy rule for a second
    run to clear. It now runs first and falls through: one run retracts, wires the
    tier, and sweeps legacy rules, and the summary stops describing publish and
    edit as "commands that no longer exist".

    tenjin doctor resolves publish.mode from the project .tenjin.json like
    config get and publish do. It read the global file and env only, so inside a
    repo pinned to review under a global auto it reported the machine as needing a
    grant the next publish there would never use.

    The install prompts are shorter. The permissions question is two sentences and a
    link rather than a tier inventory, no prompt recites a Bash(...) rule an
    operator has not met yet, the search-hook question drops its "(Escape skips,
    registering nothing)" hint, and the summary no longer promises that "your harness
    still shows each command for approval", which the same mode writes a rule to
    remove.

    tenjin search --json no longer writes the publish-back hint to stderr. It went
    straight to the stream rather than through the human rendering, so the flag whose
    help promises to "suppress human stderr rendering" left ~260 bytes of prose beside
    every MISS envelope.

    tenjin uninstall stops contradicting itself. Its help and its kept list both
    claimed nothing under ~/.tenjin is touched, while the same run correctly deleted
    ~/.tenjin/hooks/*.mjs and listed them under scripts. Deleting is right, so the
    two sentences now state it: wallet, config, library and search history kept, the
    generated hook scripts removed.

    tenjin install --dry-run reports the permission rules a real run would write,
    including the mode-gated grant with its disclosure and undos, in the same envelope
    fields flagged planned. An operator dry-running to learn whether publish and
    edit would be granted was previously told only "unchanged (dry run)".

    Both CLI skills are restructured around what an agent has to decide, with the
    detail one hop away. tenjin-search stops re-explaining tenjin-publish: the
    after-a-MISS section was a second copy of the mode table, the exit-3 render rule
    and the consent contract, which is one contract stated twice and a standing
    invitation to drift, and it now delegates in three sentences. tenjin-publish
    leads with the consent mode as a table, replaces two detector catalogs with the
    triage that actually helps (which warnings are ordinary in technical writing,
    ...

Read more

v0.1.0-alpha.13

v0.1.0-alpha.13 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 16 Aug 03:44
e1a874f

Minor Changes

  • a38f843: Self-update from the CLI, and tell agents when to use it.

    tenjin update installs the newest version npm offers this build, pinned to the
    exact version the registry names; --check only reports. The target is the
    newest of the build's own channel tag and latest, because which tag a publish
    lands on is a property of the release pipeline rather than of the version
    number: alpha sat on 0.1.0-alpha.7 while 0.1.0-alpha.8 through .11 shipped on
    latest, and a channel-only lookup would tell every alpha user they were
    current. The daily check resolves the same way, so the two cannot disagree.

    The manager that owns the install performs it: npm, pnpm, and bun are each
    driven with their own global-add command, since the PATH race that makes a
    second install dangerous comes from cross-manager writes rather than from asking
    the owner to do what it already owns. What cannot be driven is refused with the
    instruction that is correct there instead of writing a global you never had: a
    source checkout updates by git, an npx run has nothing installed to replace, a
    project-local copy updates where it is declared, and yarn is refused because
    yarn global add exists only in yarn 1. --check answers from all of them.

    The daily check now reaches agents, not just humans. Alongside the dim stderr
    line a TTY gets, a newer version appears as updateAvailable (with current
    and latest) on the JSON envelope and on the generated hook scripts' output, so
    the agent driving a command can run tenjin update itself at a moment it picks.
    Nothing installs on its own: a CLI that starts a fresh process per invocation
    has no deferred-activation window to hide a binary swap in, and a silent version
    swap reshapes output exactly where nobody is watching. New update.mode config
    key, nudge (default, all three surfaces) or off (none, and no request to
    npm). The field is read from the check's cache, so it costs no request and no
    delay on the command carrying it.

    Every install runs as a binary or node <script> and never through a shell,
    which is also the only form win32 can spawn, bounded by a 5 minute budget, from
    the home directory, with the manager's output sanitized before it reaches a
    terminal. tenjin update is documented as never-allowlisted for agents: it
    replaces the binary the agent then runs.

Patch Changes

  • dcc6b5a: tenjin-publish gains the harvest ask (tenjin-agent#145 item 2, redesigned):
    "anything from our recent work worth publishing?" now routes into the skill
    explicitly, with a sweep procedure — grade what is visible in the
    conversation against the sell rubric, offer survivors once as one batch, a no
    is final and never re-asked, and never dig through transcripts or archives
    the user did not hand over. This replaces the weekly scheduled-harvest design
    outright: an OS-level cron written during onboarding has no precedent and
    reads as intrusive, out-of-session runs create a session-archive dependency,
    and per-session nudge telemetry shows unprompted reminders do not convert.
    In-context, user-invoked, zero scheduling. One trigger clause added to the
    always-loaded description; the procedure lives in the body.

v0.1.0-alpha.12

v0.1.0-alpha.12 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 15 Aug 02:55
7e6eb4b

Patch Changes

  • a402916: Post outcome reports to /api/searches/:id/outcomes, the path the server now
    documents after BackTrackCo/tenjin#616 dropped the /agent prefix. The contract
    fixture and the live drift pin move with the client, so the scheduled
    contract-drift run goes green again.

    No fallback: tenjin serves the old /api/agent/searches/:id/outcomes spelling as
    a real alias onto the same handler for one deprecation window, so both spellings
    answer identically today and the pinned path is the one that survives the window.

  • c315e09: Widen the supply/demand triggers (tenjin-agent#145 item 1) by restructuring
    both skill descriptions rather than appending to them, holding length at
    parity with the previous wording. tenjin-search now leads with both search
    moments: before regenerating expensive research, and before empirically
    probing a third-party library or platform's undocumented behavior; the skip
    list narrows from "the thing in front of you" to "your own code" and the two
    skip sentences merge into one. tenjin-publish reorganizes its triggers into
    three routes in (user ask, after-a-MISS, unprompted work worth selling), the
    unprompted route covering substantial research and empirical proofs the docs
    don't state. Two bundled fixes surfaced by the eval work: the requires-CLI
    sentence becomes an explicit stand-down ("does not apply and must not fire,
    not even to route the question"), taking the pre-existing no-CLI defer red
    from 1/4 on main's wording to 2/4 in two independent samples; and preflight's
    mirror-drift check now only gates runs that install the vendored tenjin
    mirror, so non-mirror skill evals no longer need --no-preflight during #88.
    Clean-room results: trigger eval extended 20 to 22 cases (one per side of the
    new probe boundary) and scores 22/22 under the final wording, in-sample as
    with the previous tuning. The two defer queries still firing both carry an
    explicit user directive to use Tenjin while this skill is the only Tenjin
    skill installed in the eval room; that residual is documented on the PR
    rather than fought with more description weight.

v0.1.0-alpha.11

v0.1.0-alpha.11 Pre-release
Pre-release

Choose a tag to compare

@x402r-release-bot x402r-release-bot released this 12 Aug 05:21
5454568

Minor Changes

  • 62c2c25: Card-fund the wallet from the terminal. tenjin fund [amountUsd] signs a SIWX
    proof with the wallet's own key, asks the Tenjin backend to mint a Coinbase
    Onramp checkout URL for that same address, prints it, opens it in the default
    browser, and polls the Base USDC balance until the purchase lands (--no-open
    and --no-wait opt out). The link is single-use, expires in about five
    minutes, is bound to this machine's network, and requires a Coinbase account
    to complete; only https://pay.coinbase.com URLs are ever opened. A matching
    tenjin_fund MCP tool mints the link and returns it for the agent to hand to
    the human (no browser open, no poll; minting moves no money and the payment
    itself happens on Coinbase's authenticated page). send stays off MCP.

    The link goes to stderr the moment it is minted, on every surface, because the
    stdout envelope that also carries it is written only after the poll and the link
    does not live that long. Opening a browser and polling are interactive
    behaviours: both default off when stdout is not a TTY, so a piped, --json, or
    MCP run returns as soon as the link exists. pollStatus on the envelope
    distinguishes skipped, unavailable, timed-out and arrived instead of
    collapsing three of them into funded: false. tenjin fund is never
    allowlisted for Bash: a prefix rule would clear --base-url with it, which the
    MCP tool's amount-only input does not.

  • 5e1148e: Remove the candidate pen. Implements #133.

    A publish decision is made once. Parking a draft "for later" turned that into a
    backlog nobody returned to, and the reminder that would have resurfaced it is the
    repeat-nag class this CLI stopped emitting — so rather than warn about the pen for
    a release, it is gone now, in the same release as the skills that stopped teaching
    it.

    Removed: tenjin candidate add, list, and drop; the tenjin publish --candidate path; the local candidate store; the tenjin_candidate MCP tool;
    and the Bash(tenjin candidate list:*) line from the recommended free-verb
    allowlist, which is now eight rules rather than nine. If an earlier version wrote
    that rule into your ~/.claude/settings.json, the next tenjin install removes
    it and says so: a grant for a command that no longer exists is bloat we created,
    so clearing it is our job rather than yours. tenjin uninstall reclaims it too.
    Nothing else in that file is touched — rules you added yourself, other keys, and
    their order all survive.

    The Stop hook's open-loop reminder and the MISS publishBack hint keep the shape
    they took when parking was deprecated: publish it back, or close the loop with
    tenjin outcome --search-id <id> --status regenerated. Nothing is saved to come
    back to, and the outcome report is what marks the search resolved so the reminder
    never raises it again.

    Anything already in ~/.tenjin/candidates/ is left exactly where it is. It is
    your content, so nothing deletes it and tenjin uninstall will not either — but
    nothing reads it any more, so a draft you want is a file to open at
    ~/.tenjin/candidates/<id>/draft.md and publish with tenjin publish <file.md>.
    The candidate resolution value stays parseable in the search ledger so an older
    searches.json still loads; nothing writes it now.

  • 5cc75d2: Make the MISS → publish loop closable on the path agents actually take. Until now
    only tenjin publish --candidate <id> could name the search it answered, so the
    bare file publish that both the Stop hook and the auto-mode skill prescribe left
    the search open in the local store and the reminder kept firing at work that was
    already on the marketplace. tenjin publish <file.md> --search-id <id> now closes
    it, and prefills the searched question into the answer card's questionsAnswered
    when neither --question nor the draft's frontmatter names one — that phrasing is
    what the next searcher sends, so it is the right fallback. The prefill is dropped
    rather than truncated when the stored question runs past the card's 200-character
    item bound, because a prefill nobody asked for must not fail a publish that was
    otherwise fine. --search-id with --candidate is a usage error (the candidate
    already carries its own searchId), a malformed id refuses before any wallet touch,
    and an id the local store does not know still publishes and says so. What happened
    is reported in both registers: a stderr line for a human, and a
    search: { id, closed, prefill } field on the JSON receipt, because --json
    suppresses the stderr notes and an agent that named a search could otherwise not
    tell a closed loop from an open one, nor learn that its question was too long to
    become a card entry. closed reports the OUTCOME of the local write rather than
    the intent to make it: the store update is best-effort and never throws, so a
    lock it could not take comes back as closed: false and a line naming the
    command that closes the loop by hand.

    A --draft now parks privately on BOTH paths and closes nothing. It leaves the
    named search open, and — this is a behavior change — it also leaves a
    --candidate draft parked instead of clearing it. A draft answered nobody, so it
    is not the publish that retires the pen entry; the later real publish is. The
    receipt reports cleared: false with no warning, which is how a deliberate hold
    reads against a clear that failed.

    Every surface that hands you the closing command now hands you a complete one. A
    MISS's publishBack payload emitted tenjin publish <file.md> --json beside a
    park line that already carried the id, so the arm the agent was most likely to
    copy was the one that closed nothing; both arms, and the stderr line beside them,
    now name the searchId.

    The Stop hook's open-loop reminder is reworded to match. It named a flag that did
    not exist (tenjin publish, searchId <id>) and it carried no safety qualifier, so
    it nagged just as hard on a question about private infrastructure as on a durable
    public finding. It now emits the real command and conditions the publish arm on
    the finding being public, reusable and rights-clean, with parking as the other
    arm. The batched line for WebSearch-hook misses gets the same command syntax.

    The reminder is now scoped to the session that opened the loop. The search
    ledger is machine-global, so the Stop hook was raising whichever open MISSes it
    found at whatever session happened to stop next, including a sibling session's
    work that this one never did and cannot close. A stored search now carries an
    optional sessionId, and the Stop hook parses the session_id its turn-end
    payload already carries and skips entries stamped with a different one — without
    marking them nagged, so the session that owns a loop still gets its single
    reminder. The scoping is deliberately one-directional: an entry nothing could
    attribute is still raised in every session, so a loop can never go invisible in
    all of them at once, and a payload that is malformed or names no session falls
    back to exactly the old machine-global behavior.

    Both recorders stamp. The WebSearch hook reads the session_id the harness puts
    on its stdin, and a deliberate tenjin search reads the same value from
    CLAUDE_CODE_SESSION_ID, which Claude Code exports to Bash tool subprocesses, so
    a CLI search and a hook search in one session carry the same stamp.
    TENJIN_SESSION_ID overrides it for anyone wiring this up by hand. On a harness
    that exports neither, a search records no session rather than guessing one, and
    falls back to being raised everywhere.

    tenjin publish --excerpt sets the public preview. The excerpt is what every
    non-buyer reads — the 402 preview, the directory feed, search results, RSS, the
    social card — and the server only derives it from the body's leading prose when
    the client sends none. Frontmatter excerpt: already reached the wire; the flag
    did not exist, so the one-shot path an agent actually uses had no way to say what
    a stranger sees, and a piece whose answer sat too high leaked its verdict for
    free. --excerpt wins over frontmatter, both are refused over the server's
    500-character bound rather than truncated (a silently cut preview is a different
    preview), and the refusal now happens at the command's edge instead of inside the
    request builder, so it costs a message rather than a keystore unlock.

    Every free-text field that ships is stripped of control bytes, escape sequences
    and bidi overrides. None of it is necessarily typed by the person publishing: a
    card question can be prefilled from a stored search, and the title, excerpt,
    tags and every card field can arrive over MCP from an agent that read them off a
    fetched page. trim() removes neither a CSI sequence nor a right-to-left
    override, so without this a payload rides into the marketplace and renders in
    every future reader's terminal. The strip lives in the two request builders that
    publish and edit share, so it covers both commands and both MCP tools by
    construction rather than by each flag remembering, and it runs before the length
    bounds, since the stripped text is what the bound has to describe. The post BODY
    is deliberately left alone: that is the author's own markdown, and rewriting it
    is a content change nobody asked for. Ordinary unicode, including emoji ZWJ
    sequences, is untouched.

    The MCP tenjin_publish tool now forwards searchId and excerpt to the
    command core. The tool advertised both, because the input schema...

Read more