Journeyman
Turn any agent into a self-learning system. Your agent learns from user feedback — and every lesson must pass your evals before it goes live. Two lines of integration code.
pip install journeyman-agents (local dev: pip install -e ".[dev]")
import journeyman as jm
jm.init() # auto-instruments openai / anthropic
@jm.agent # <- your agent. Any Python callable.
def my_agent(question: str, learned_context: str = "") -> str:
# put learned_context into your prompt — that's the whole self-learning hook
...
my_agent("How do I reset my password?") # traced
jm.feedback(1.0, "correct") # one-line learning signal
journeyman demo # see the full flywheel on sample data (no API keys needed)
journeyman learn # run one eval-gated learning cycle
journeyman up # local API server (HTTP mode for other processes/languages)
What it does
| Layer | What you get | Code required |
|---|---|---|
| Tracing | Every run: inputs, outputs, tool calls, LLM calls, tokens, cost, latency, errors | jm.init() + @jm.agent |
| Feedback | Explicit signal bound to traces | jm.feedback(score, comment) |
| Memory | Long-term memory built automatically from your agent's experience + your docs | none |
| Insights | Lessons learned from feedback, injected back into your agent | a learned_context: str = "" parameter |
| Evals | Suites in YAML/DB, 7 scorer types incl. LLM-as-judge, full history | a YAML file |
| The gate | Learned changes only stay if the eval pass-rate holds — otherwise auto-rollback | mark a suite gate: true |
| API server | HTTP trace ingest + read/manage APIs, for other processes and languages | journeyman up |
The flywheel
your agent runs users react
┌───────────────────┐ ┌─────────────────┐
│ @jm.agent traces │──────────► │ jm.feedback() │
└───────────────────┘ └────────┬────────┘
▲ │
│ ▼
┌─────────┴─────────┐ ┌─────────────────┐
│ learned_context │ │ learning cycle │ journeyman learn
│ (lessons your │ ◄──────────│ new lessons │
│ agent earned) │ applied │ EVAL GATE │──► rolled back if
└───────────────────┘ only if └─────────────────┘ pass-rate drops
evals hold
The learning cycle turns feedback into candidate lessons, reconciles them with what the agent already knows, and runs your gate suite before anything goes live — regressions roll back automatically. Well-rated production answers are also mined into suggested eval cases you can accept into a suite with one API call.
Works with zero config — scales with config
- No API key? Everything runs offline with built-in heuristics. Add
OPENAI_API_KEY/ANTHROPIC_API_KEYand the same pipeline upgrades itself to LLM-powered learning, LLM-as-judge, and real embeddings. Your agent's own LLM stack is untouched either way. - No server? The SDK writes to an embedded SQLite db (
.journeyman/).journeyman upreads the same file. SetJOURNEYMAN_SERVER=http://host:portto ship traces over HTTP instead (works from other processes/languages —POST /api/traces). - No config file? Fine.
journeyman initwrites an optionaljourneyman.yaml:
project: my-bot
entrypoint: app.main:my_agent # lets the CLI/server run your agent for evals
learning:
gate_suite: smoke # the eval gate
regression_threshold: 0.05
auto_minutes: 0 # >0: server runs learning cycles on a timer
Eval suites
# suite.yaml -> journeyman eval --load suite.yaml
name: smoke
gate: true
cases:
- input: "How do I reset my password?"
expect_contains: "portal.acme.com"
- input: "VPN isn't connecting on my Mac"
expect_contains: "6.2"
- input: "Summarize our refund policy"
judge: "Answer must state the 30-day window and the store-credit exception."
Checks: expect_exact, expect_contains, expect_contains_any,
expect_not_contains, expect_regex, judge (binary LLM-as-judge), and
scorer: module:fn for custom Python.
Safety guarantees of the SDK
- Never raises into your code; internal failures degrade to no-ops (one warning).
- Never blocks your hot path — persistence happens on a background thread with a
bounded queue (drops, never blocks).
jm.flush()for scripts/serverless. KeyboardInterrupt/SystemExitalways propagate; your exceptions are recorded and re-raised unchanged.JOURNEYMAN_DISABLED=1is a true kill switch.jm.status()tells you exactly what's running and where data goes.
Other languages
The learning loop runs in the server, so other languages need only a thin client. Official SDKs, same safety contract as Python (never throw, never block, kill switch):
- TypeScript / JavaScript —
sdks/typescript(Node 18+, zero dependencies) - Go —
sdks/go(standard library only)
import * as jm from "journeyman-agents";
const myAgent = jm.agent(async (q, learnedContext) => { /* your agent */ });
await myAgent("How do I reset my password?");
jm.feedback(0.0, "wrong — the portal moved");
Anything else can speak the HTTP API directly: POST /api/traces,
POST /api/feedback, GET /api/recall?q=... against journeyman up.
Full guide for all three paths: docs/SDKS.md.
Python API
jm.init(project=..., autolog=True, server=None, db=None) # all optional
@jm.agent / @jm.tool / @jm.step # decorators (bare or with args; sync or async)
jm.wrap(fn) # functional form
with jm.trace("name", input=...): # for code you can't decorate
jm.feedback(score, comment, trace_id=None)
jm.recall("query") # the learned-context block, on demand
jm.evaluate(fn=None, suite=None) # run a suite from Python
jm.learn() # run a learning cycle from Python
jm.record_llm(model, input, output, tokens_in, tokens_out) # custom LLM clients
jm.flush(); jm.status()
How much better does it get?
A working retrieval agent — it reads documents, extracts answers, picks options — answers four public benchmarks. It gets most of them wrong, which is the point: that's the starting line. Users correct its mistakes on the original wording, one learning cycle runs, and then it faces the same questions reworded, so it can't have memorised anything.
| Benchmark | Scored by | Before | After |
|---|---|---|---|
| SQuAD (40-document corpus) | F1 | 18% | 78% |
| HotpotQA (two-hop, 8 decoys per question) | F1 | 8% | 100% |
| MMLU (college & professional exams) | exact match | 18% | 75% |
| ARC-Challenge (science reasoning) | exact match | 20% | 88% |
| Average — 200 questions | 16% | 85% |
16% → 85%, a 5.4× improvement, from 174 corrections and one learning cycle
that takes 12 seconds. Both columns are the same agent on the same reworded
questions; the only difference is whether it has the lessons. Reproduce:
python bench/improve.py.
What happens when the feedback is wrong?
This is the question every "learns from your corrections" tool skips, and it's the one that costs money. Take an agent that already works, hand it 10 confidently wrong corrections per benchmark, run a learning cycle, and measure how much of it still answers correctly:
| Answers still correct after 70 wrong corrections | Keyword log | Journeyman |
|---|---|---|
| Across all questions | 74% | 97% |
| On the questions the bad feedback targeted | 0% | 91% |
| On untouched questions | 99% | 99% |
Every batch is tested against your eval suite before it goes live and kept
only if the pass rate holds. A lookup table has no equivalent — it applies
whatever it's told, immediately. Reproduce: python bench/robustness.py.
Weakest benchmark: MBPP at 82%. The gate is exactly as good as the evals you give it, and 20 cases didn't cover enough of the poisoned tasks. Full detail in docs/BENCHMARKS.md.
Development
python -m venv .venv
.venv/Scripts/python -m pip install -e ".[dev]"
.venv/Scripts/python -m pytest tests
Docs: docs/BENCHMARKS.md (what's measured and how), docs/SDKS.md (TypeScript, Go, and HTTP integration), CONTRIBUTING.md.
Metadata
Release files for journeyman-agents 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| journeyman_agents-0.1.1.tar.gz | 995.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| journeyman_agents-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.0 MB
Release files / journeyman_agents-0.1.1.tar.gz
| Download URL | journeyman_agents-0.1.1.tar.gz |
|---|---|
| Size | 995.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
106ec131b3b9363907c14276eb5affd0c7760936cb23ccc54b41b97072b58789
|
|
BLAKE2b-256 checksum How to use checksums |
821215a5f758ba10171d83f032344281ac88f0d008b2174341ae241216d72cce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / journeyman_agents-0.1.1-py3-none-any.whl
| Download URL | journeyman_agents-0.1.1-py3-none-any.whl |
|---|---|
| Size | 53.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
816c2a2748e1aa5a9f59f1629b7413b60354c885bcfcf95dd2d6a4f27dc1ad3b
|
|
BLAKE2b-256 checksum How to use checksums |
9faf944986fa6193b762a1ce052fb5c2e7f78804b0c6f5c8927a0da517a27c8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for journeyman-agents 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.