An assistant that writes its own skills when it runs out of them.
It runs on your machine, does real work on it, and when a task needs something it cannot do, it says so, asks, and compiles the missing piece — each one checked inside a container before it is allowed near your files.
Install · How it works · Writing skills · Safety · Limits · Site
Asked for something it has no skill for, JumaBek notices, explains what is missing, and asks before writing anything. Recorded, not reconstructed:
The permission prompt leads with what it is about to do, not with the task. Everything after it — the container check, the build, the validation — runs unattended, and the skill stays installed and loads on every later run.
| Windows |
irm https://raw.githubusercontent.com/jumabek-dev/jumabek/main/install.ps1 | iex |
| Linux, macOS |
curl -fsSL https://raw.githubusercontent.com/jumabek-dev/jumabek/main/install.sh | bash |
The installer puts everything under ~/.jumabek, adds it to your PATH, and never overwrites
a config you have already edited. It names the kinds of endpoint that work and installs none
of them for you.
From source, if you would rather:
cargo install --git https://github.com/jumabek-dev/jumabekThen set a key and start:
export JUMABEK_API_KEY="your-key" # or put it in ~/.jumabek/secrets.toml
jumabekRe-run the installer. It replaces the binaries and leaves config.toml and secrets.toml
alone, because those are yours once they exist.
prompt.md is the exception, and it has to be. It is how the model learns what it is
allowed to do, so a release that adds an action adds it there too — and a copy that never
moves ships a capability nobody can reach. Nothing fails; the feature is simply invisible.
So the binary carries the prompt it was built with and keeps the last reconciled one beside
yours as prompt.md.release. On startup:
- your copy is untouched → it is brought forward, and one line says so;
- your copy has your edits and the release has not moved → nothing is said;
- your copy has your edits and the release has moved → you are told, and nothing is
overwritten.
jumabek doctornames both files to compare.
Merging your own words into a new prompt is not something to guess at, so it is never guessed at.
| Dependency | Without it |
|---|---|
| An OpenAI- or Anthropic-compatible endpoint | nothing works |
| Rust toolchain | it runs, but cannot write itself Rust skills |
| Python 3 / Node | the same, for skills in those languages |
| Docker | new skills are refused, because they cannot be checked first |
| ffmpeg | voice is unavailable; typing still works |
jumabek doctor reports all of it, and says what each gap costs you:
ok home ~/.jumabek
ok config ~/.jumabek/config.toml
ok API key found
ok LLM http://localhost:11434/v1 · qwen3.5:4b
ok intelligence one model for everything
ok Rust cargo 1.96.0 — skills can be written in Rust
ok Python Python 3.12.4 — skills can be written in Python
WARN Node node not found
JumaBek runs, and can still write skills in the other languages
WARN Docker docker is installed but the engine is not running
new skills are checked in a container before they touch your machine;
without it building them is refused
ok skills 2 installed: shell_executor, rss_parser
7 ok, 2 warning(s), 0 failure(s)
JumaBek will run; the warnings above disable parts of itNote
Any OpenAI- or Anthropic-compatible endpoint. Point [llm].base_uri at a local runner
(Ollama, LM Studio, llama.cpp), at a router in front of several providers, or at a provider
directly. By default the client speaks OpenAI's shape — sends model, messages and
stream, reads choices[0].message.content — nothing beyond that is assumed. Set
protocol = "anthropic" to speak /v1/messages instead: system separated out, x-api-key
instead of Authorization, content blocks instead of choices. Ollama and the real
Anthropic API both answer there.
Write the address the way its own documentation gives it: with /v1 on the end or
without, both land in the same place. An endpoint that wants no API key needs none —
leave it unset and no auth header is sent.
What a local model does not get you for free is the agent itself: every turn has to come back as one JSON object in a fixed action format, and a small model will miss it often. Local is a real option for routine work; treat "it connects" and "it can drive the loop" as separate questions.
Every skill is a separate process. JumaBek writes a line of JSON to its stdin and reads a line back from its stdout.
That is the whole contract, and it buys several things at once.
| Any language | A skill is whatever speaks the protocol — including the ones JumaBek writes itself, in Rust, Python or Node. |
| Nothing to rebuild | Adding a skill means dropping a binary in a folder. The agent itself is never recompiled. |
| Crashes stay local | A skill that hangs is killed and restarted on the next call. It cannot take the agent down. |
| Lazy by default | Descriptions are cached, so twenty installed skills cost one millisecond at startup instead of seven hundred. |
Turning a light on and writing a skill are not the same kind of thinking, and paying for them at the same rate is not a decision anybody makes deliberately. Name three models and JumaBek moves between them.
[llm.intelligence]
low = "cc/claude-haiku-4-5-20251001"
medium = "cc/claude-sonnet-4-6"
high = "cc/claude-opus-4-8"
default = "medium"The section is optional. Name none and nothing changes — one model, as before. Name only some
and switching stays off, with jumabek doctor saying which level has no model behind it; a
level that cannot be reached is worse than no levels at all.
low is one skill call and done. medium is the default: several steps, a search, files,
skills chained together. high is for writing a skill, or for anything that already failed a
level down.
The model can move itself, freely downwards and upwards with a stated reason. What it cannot do is decide the cases that matter, because a model too weak for a task is in no position to notice. So the core moves it, on events rather than opinions:
| Writing a skill | Always the highest level, before the first line of code. A cheap model has no way of telling that the skill it just wrote is bad, and the cost of being wrong is a binary that lives on your machine for months |
| Two unreadable answers | The model is not holding the response format. Another attempt at the same level is a wasted turn |
| A build that keeps failing | The code is above this level |
| The same call three turns running | Not a long job, a stuck one — repeating a call is the actual signal, not the iteration count |
| Nearly out of iterations, nothing else caught it | A last-resort net for a model that never escalates itself |
| Nobody at the keyboard | Scheduled jobs and anything arriving through the inbox start at low |
An escalation that was not the task's fault does not spend an iteration. The turn failed because the level was wrong, and charging the task for that would quietly make the cheaper level worse than never switching at all. Every task starts again at the default, so one hard afternoon does not leave the expensive model running all week.
Each answered turn records the level it ran on, and the turn where the level moved records why — otherwise there is no way to tell whether any of this earns its keep.
The number printed while it works is a local estimate, made before the request is sent so it can decide what fits. What the provider counted is a different number, recorded separately and labelled as such — on Cyrillic against a Claude model the two differ by about ten percent, which is a tokenizer that does not belong to that model.
$ jumabek tokens
cc/claude-opus-4-6 · openai
3 turn(s) · 61959 in · 63 out — counted by the provider
56220 in — guessed locally before sending
caching: 58864 read, 0 written, over 3 of 3 turn(s)The standing part of the context — the system prompt and the pinned facts — is marked for caching, and everything that changes each turn sits after that mark. Put the mark on the wrong side and every turn is a miss. In a real conversation about ninety-five percent of the input comes back from cache, and the uncached part stays around a thousand tokens however long the conversation gets.
A provider that reports nothing about caching is recorded as silent, not as a miss — those are different facts and only one of them is a problem.
The cache belongs to one model. Dropping to a cheaper one mid-task and coming back pays to re-read the whole context twice, so it is refused; going up once and staying there is not.
Each level can point at its own endpoint instead of sharing [llm].base_uri — a local Ollama
for low, Ollama Cloud or anything else for medium and high:
[llm.intelligence.endpoints.low]
base_uri = "http://localhost:11434"
protocol = "openai"
reasoning_effort = "none"
context_token_limit = 128000
[llm.intelligence.endpoints.high]
base_uri = "https://ollama.com"
protocol = "anthropic"protocol is openai (/v1/chat/completions) or anthropic (/v1/messages — Ollama and the
real Anthropic API both speak it). Anything left unset — base_uri, api_key (kept in
secrets.toml, under [llm.levels]), reasoning_effort, context_token_limit,
structured_output — falls back to [llm], the same "name none, nothing changes" rule as the
model names above.
Where the protocol supports it, structured_output (on by default) asks the provider for JSON
matching the exact action schema — response_format on an OpenAI-shaped endpoint, a forced tool
call on an Anthropic-shaped one — instead of trusting the model to remember "no markdown fence."
Where a provider does not honour that, JumaBek reads a plain-text reply the way it always has.
Everything said is kept in SQLite. The current session is always in context; older sessions
are searched only when the model asks, through a full-text index with Russian and English
stemming — so файл finds файлами, and file finds files.
When a conversation outgrows the context window, the oldest exchanges are dropped in whole task groups — never half of one, which would leave a result with no matching command — and replaced by a marker telling the model what it can still recall.
Facts it chose to keep sit in front of it every turn. Writing a key again replaces what was there, so a fact that changed stops being two facts that disagree; when a key honestly holds two values, the model says so and both stay. A fact can be pinned so it is always present, and scoped to a language or a project so one project's details do not bleed into another.
By default every fact is loaded every turn. Set [memory] retrieval = true to pick them by
meaning instead: pinned ones always, then whatever is closest to what is being discussed.
The model doing the picking runs on this machine and nothing leaves it. It is not in the
released binaries — the ONNX runtime it needs has no build for every target we ship — so it
takes cargo build --release --features retrieval, and jumabek doctor says plainly if the
setting is on and the binary cannot do it.
Some work is worth doing but not worth reading. Scanning forty log files fills a context window with output whose only useful part is the conclusion.
So JumaBek can hand a piece of work to a copy of itself. The copy starts empty — the system prompt, the skills, and a task written as a standalone instruction. It cannot see the conversation it came from, which is the entire point.
Spawning returns immediately. The copy runs on its own while the conversation carries on, and its summary — never its transcript — arrives on a later turn, often after you have already been answered.
▌ Handing that off.
· subagent · read every .log under C:/logs and list the error codes
▌ Handed off; I am free again.
· the agent you spawned for 'read every .log' finished in 12s and reported: 41 files,
three distinct codes
Nobody is at the keyboard on the copy's side, so it cannot ask a question or ask permission; anything it tries is refused. Give it work it can finish alone. Nesting stops at two levels — below that, a tree is almost always a task that failed to decompose.
If it dies, that is reported as a death rather than dressed up as a result. If you quit while one is still working, you are told how many were dropped.
A role is three things named in config.toml: a short brief, a list of skills, and what it
may do.
[roles.researcher]
prompt = "You find things out and report what you actually saw. You never guess."
skills = ["searxng_search", "shell_executor"]Spawning can name one. The copy's rights are then the parent's narrowed by the role's — never the union. Without that, an agent with no shell access could launder its way to a shell by asking a peer, and nothing in the record would show a permission was ever granted.
Everything spawned from one task shares a group: one goal, one board, and one pot of iterations for all of them together. The shared pot is not decoration — three agents each comfortably under their own limit will otherwise pass work between themselves indefinitely. When it runs out the whole group stops and writes down that it did.
The board is a SQLite table scoped to that group, and it is the single record: a conclusion that was not written to the board did not happen. Entries are a task, a finding, a decision or a question, addressed to an agent, a role or everyone. Agents can also message each other directly, bounded by a fixed number of turns per pair; when those run out the exchange closes, a decision goes to the board, and the disagreement goes up to whoever spawned them.
$ jumabek agentsThree panes in a second terminal: agents on the left coloured by state, their group's board
on the right, and how much of the shared budget is gone along the bottom. Arrow keys pick an
agent and the other two follow it. --once prints a plain table and exits, for scripts.
It reads a token-free endpoint on the same loopback port, so nothing new is exposed.
An agent that runs into something outside its grant does not just fail. It can ask, naming what it needs and why — a person may be reading that sentence hours later.
A request inside the ceiling in config.toml and not marked critical is settled by the main
agent at once. A critical one goes to you if you are there, and up to whoever spawned it if
you are not. Nothing at runtime can raise the ceiling; that takes editing the file and
restarting, which is what stops privilege accumulating one justified step at a time.
Every verdict is written down against whoever asked:
$ jumabek rights
2026-08-30T08:27:11Z · inbox:te wanted searxng_search · granted by main agent · the answer
is only on the web
2026-08-30T08:27:11Z · inbox:te wanted may write new skills · refused above the ceiling by
config · I would like to write a skillA job is work that outlives the prompt: a reminder, a recurring check, a folder being watched. Jobs live in SQLite and come back after a restart — most of what makes a reminder worth setting.
| Schedule | Meaning |
|---|---|
in 3h |
once, three hours from now |
at 2026-07-30T09:00:00Z |
once, at a moment |
every 30m |
repeating, minimum 10s |
cron 0 9 * * 1-5 |
five fields: minute hour day month weekday |
watch ~/Downloads |
when something there appears, changes or disappears |
Watching polls and compares name, size and modification time. Filesystem events would be sharper, but they cost a dependency and a debouncing problem to save a few seconds on a job that runs every quarter hour. The first look only learns what is there — otherwise every watch would fire once at startup on a directory nobody touched.
Jobs report into the live session through rustyline's external printer, which redraws the line you are typing underneath the message instead of through it.
Skills answer when called. That is fine until a skill is holding something live — a chat connection, a folder, a webhook — and needs to speak first.
So the core listens on 127.0.0.1. Anything on the machine with a token can push work in:
$ curl -H "Authorization: Bearer $TOKEN" -d '{"source":"telegram","kind":"notify",
"text":"Асия: буду через час","who":"asiya"}' localhost:20129/notify
{"status":"queued from telegram"}/notify queues something that happened and answers immediately. /ask runs the request and
returns the reply over the same connection — which is how a bot JumaBek writes for itself
gets its answers back.
Three locks, and each is load-bearing:
The address is not configurable. Loopback, in a constant, with a test that fails if it changes. A port that runs tasks on your machine is a shell; one reachable from the network is somebody else's shell.
Every caller has its own token. Tokens live in secrets.toml, one per caller, so a
compromised one is revoked without touching the rest. Under 24 characters is ignored and said
so out loud, because a token quietly dropped is worse than one refused.
A grant is required, not optional. Rights live in config.toml and belong to the token,
never to the request — a caller cannot widen its own permissions by asking. Inbound work runs
under the same rules as a background job: it cannot ask you anything, and it cannot step
outside its list.
The model can ask for a key for a skill it wrote, and the core generates it, writes it and hands it over — the model never sees the token and cannot edit those files itself.
Each source and person is a running conversation. Turns that arrived from the same place and the same person are threaded together and survive a restart, so "как в прошлый раз" from a Telegram chat means the last time in that chat. That thread is separate from what you type in the terminal: the terminal sees the inbox turns, the inbox side sees only its own.
config.toml, secrets.toml and prompt.md are watched. Save one and it is picked up
within a few seconds:
· reloaded config.toml
· max_iterations 10 -> 14
· inbox now admits telegram, relayIteration limits, the model and endpoint, the API key, the prompt, inbox tokens and grants, and per-skill settings all take effect live — a skill whose settings changed is restarted, since its environment is handed to it when its process starts and nothing else reaches it.
Two things need a restart, and say so rather than silently doing nothing: the database path, because the session is open inside it, and the inbox port, because the listener is already bound.
You can write one yourself, or let JumaBek do it. Either way it is one file.
use jumabek_sdk::{MethodInfo, ModuleMetadata, SkillError, SkillModule, SkillOutput};
struct WordCount { metadata: ModuleMetadata }
#[async_trait::async_trait]
impl SkillModule for WordCount {
fn get_metadata(&self) -> &ModuleMetadata { &self.metadata }
fn health_check(&self) -> bool { true }
fn available_methods(&self) -> Vec<MethodInfo> {
vec![MethodInfo {
method: "count".to_string(),
description: "Count the words in a piece of text".to_string(),
args_description: "The text to count".to_string(),
}]
}
async fn execute(&self, method: &str, args: &str) -> Result<SkillOutput, SkillError> {
match method {
"count" => Ok(SkillOutput::Text(args.split_whitespace().count().to_string())),
other => Err(SkillError::NotFound(format!("unknown method '{}'", other))),
}
}
}
#[tokio::main]
async fn main() {
jumabek_sdk::runtime::run_skill(WordCount { /* ... */ }).await.unwrap();
}Build it, drop the binary in ~/.jumabek/skills, and it is there next start.
A skill is a process, so the language was never the protocol's business — only the build pipeline's. JumaBek writes skills in Rust, Python or Node, and a skill it writes for itself says which:
{"type":"GenerateChunk","module_name":"weather","language":"python", ...}Rust links jumabek_sdk. Python and Node get a small jumabek helper written next to the
code, so the wire format is never reimplemented by hand:
import jumabek
def execute(method, args):
if method == "count":
return str(len(args.split()))
raise jumabek.SkillError("unknown method: " + method, kind="NotFound")
jumabek.run(name="word_count", version="0.1.0",
description="Counts the words in a piece of text",
methods=[{"method": "count",
"description": "Count the words in a piece of text",
"args_description": "The text to count"}],
execute=execute)The helper points print and console.log at stderr, so a debug line left in by accident
cannot corrupt the response the core is parsing.
Rust installs as one binary. The others install as ~/.jumabek/skills/<name>.d/ — code,
helper and dependencies together — beside a launcher named <name>. The skill layer only
ever sees an executable path, which is why nothing else in the codebase has a special case
for them.
Each language builds in its own container image and its own package cache. Override them per language when the defaults do not suit:
[preflight]
image = "rust:1-slim" # kept under its old name: this is the Rust one
[preflight.images]
python = "python:3.12-slim"
node = "node:22-slim"A language the machine does not have is refused with [TOOLCHAIN MISSING] before any code
is written to disk, and it costs the model none of its fix attempts — rewriting the code
would not have helped. jumabek doctor lists which of the three are usable here.
A skill runs with a stripped environment. It cannot see the agent's own credentials, and it
must never contain a hard-coded key. Whatever you put under [skills.<name>] reaches that
skill, and only that skill:
# config.toml # secrets.toml
[skills.weather] [skills.weather]
city = "Almaty" api_key = "..."JUMABEK_SKILL_CITY=Almaty JUMABEK_SKILL_API_KEY=...
jumabek # start a session
jumabek "how many files here?" # run one task and exit
jumabek --voice # speak instead of typing
jumabek doctor # check the setup
jumabek mic # watch the microphone level for ten seconds
jumabek where # print every path it uses
jumabek skills # list installed skills
jumabek remove <name> # remove one
jumabek jobs # list background jobs
jumabek job-stop <id> # stop and delete one
jumabek agents # watch what its agents are doing, live
jumabek agents --once # print the same thing once and exit
jumabek rights # who was granted what beyond config.toml
jumabek tokens # what turns actually cost, counted and guessed
jumabek inbox # is the door open, and who may knock
jumabek profile # what it remembers about you
jumabek forget-subject <who> # make it forget one subject
jumabek backups # list snapshots
jumabek restore <id> # roll back to oneInside a session, /voice and /cli switch modes without losing the conversation, and
/quit leaves. Shift+Enter starts a new line without submitting — Alt+Enter does the same
on terminals that do not report modifier keys.
When an agent working on its own needs something from you, /waiting lists what is
outstanding, /allow <id> and /deny <id> settle a permission, and /answer <id> <text>
answers a question. It rings the terminal when one arrives, reminds you a minute before it
gives up, and says plainly when one went unanswered — a question that dies quietly is worse
than one that was refused.
Answers are rendered, not printed: headings, lists, tables, code blocks and emphasis all arrive as terminal formatting rather than raw asterisks, and they appear as they are written rather than in one lump at the end. Your turn and the agent's are told apart by a chip against a solid left bar, so a long session stays readable.
Nothing from a background job, an agent working on its own, or the inbox is allowed to land in the middle of an answer. It waits and comes out afterwards, in the order it was said. When one does arrive while you are typing, it is printed above your line and your half-written input is redrawn underneath, untouched.
A microphone that goes unheard used to be a silent failure with nothing to look at.
jumabek mic opens the device and shows the level against the threshold it has to beat:
0 | | needs 50 quiet
39 | | needs 50 VOICE
141 |# | needs 50 VOICE
93 |# | needs 50 VOICE
loudest frame: 146
noise floor settled at: 19
complete utterances: 1
The microphone works and speech is being detected.
The signal is quiet: 146 at its loudest, where speech usually reaches
a few thousand. It clears the threshold, but transcription will be better
with the input level raised in the system sound settings.The threshold falls over the first second as the noise floor settles, so a quiet room ends up more sensitive than a loud one. A sentence has to clear the line for half a second to count, and finishes after nine hundred milliseconds of silence — which is why the check waits for you to stop talking rather than cutting at the clock.
Voice mode says the same things as it goes: when it starts listening, how long an utterance was, and when it heard something it could not make out.
Self-improvement means running code that did not exist a minute ago. Six things stand between that and your machine, and each exists because of something that actually went wrong.
Dangerous commands are stopped by the core, not by the model. Recursive deletes, disk formatting, shutdown, a download piped into a shell — all need your word, whether or not the model thought to ask. Relying on the model to volunteer is not a control: told to skip the confirmation, it skips it.
New code is exercised in a container first. Built, then run with no network, a read-only filesystem, capped CPU and memory, and every capability dropped. Code that hangs, crashes or reaches for the network is caught there rather than on your disk.
In that container the skill is also called by its own method names. It used to be enough to start, say your name and survive nonsense — so a skill whose only method answered "no such method" when asked for itself passed every check and was installed, and the model found out by calling it. Now each declared method is tried, and one that answers exactly the way the skill answers a name it has never heard of fails the build. This happens only inside the container, where there is no network and nothing to write to: calling a stranger's methods to see what they do is safe there and nowhere else.
Every install is preceded by a snapshot. Rolling back removes a skill that did not exist at that point, rather than merely restoring the files that did. The rollback itself is snapshotted first.
Skills cannot leak processes. Each runs inside a group killed as a unit, so a shell command it started does not outlive it — even if the agent itself is killed.
Nothing gets in without a grant. A background job's rights are fixed before it runs, and so are an inbound request's. Everything else here asks at the moment it matters. A job cannot: there is nobody at the prompt at three in the morning. So approving one means approving a list of skills, and separately whether it may write new skills or step past a safety rule — and the question leads with that list rather than with the task. A job that tries anything else is refused and says so in its report; it cannot delegate its way around the limit, because a sub-agent inherits the grant that spawned it, narrowed further by its role.
Rights only ever narrow sideways. When one agent asks another to do something, what the
second may do is the intersection of both, never the union — otherwise an agent without shell
access reaches a shell by asking a peer, and nothing in the record shows a permission was
granted. Needing more means asking upward instead, under a ceiling in config.toml that no
decision at runtime can raise, and every answer is written down against whoever asked.
Warning
The container is a check, not a jail. It catches broken and misbehaving code before
installation. It does not protect against a malicious build script in a dependency,
because the binary that finally gets installed is compiled natively afterwards. That is
why the config section is called preflight and not sandbox.
A local model connects; that is not the same as working. The transport has been checked against Ollama, Ollama Cloud and OmniRoute, with a key and without. What a small local model does not give you is the agent itself: every turn has to come back as one JSON object in a fixed action format, and a 4B model will miss it often. Treat "it connects" and "it can drive the loop" as separate questions.
Voice is only half proven. Capture has now met real hardware: the device is found, the
stream arrives, and speech is detected against the noise floor on a USB headset — that much
is measured, not assumed. What has not been exercised end to end is the rest of the round
trip, transcription through to a spoken answer. The race that made older assistants listen
to their own voice is fixed and measured. The detection thresholds are still tuned to one
room and one microphone; jumabek mic will tell you how yours compares.
Parallelism helps across skills, not within one. Two calls to the same skill share one connection and one working directory, so they are deliberately serialised.
Sub-agents die with the process. They run in the same program, not as separate ones, so quitting takes them with it. You are told how many were dropped rather than left to guess.
Retrieval is not in the released binaries. The ONNX runtime it needs has no prebuilt binary for every target in the release matrix, so shipping it by default would break the build for some of them. It is a cargo feature; without it every fact is loaded every turn, which is what happened before and still works.
Prompt caching depends on the provider. The markers go out on both protocols and an
endpoint that refuses them is dropped back to plain requests automatically — but whether
anything is actually cached, and whether it is reported, is the provider's decision.
jumabek tokens shows which of the two you are getting.
Three licenses, because the parts are not the same kind of thing.
| Part | License | |
|---|---|---|
The agent (jumabek) |
AGPL-3.0 | |
The skill SDK (jumabek_sdk) |
MIT or Apache-2.0 | |
The shipped skills (skills/*) |
MIT |
The agent is AGPL because the one thing worth guarding against is somebody running it as a hosted service and keeping their changes. AGPL does not forbid that; it just requires the changes to come back. Running JumaBek on your own machine, modifying it, or writing skills for it are unaffected — that is the whole point of it.
The SDK is permissive because a skill links it. Under AGPL every third-party skill would inherit the same terms, which would end the idea of an ecosystem before it started. The license boundary sits exactly where the process boundary already sits: skills are separate programs speaking a protocol, and they are yours.
Contributions require a CLA, so the licensing can still be adjusted later without hunting down everyone who ever sent a patch.
Juma — Friday in Kazakh; the one that came after Jarvis.
