Editing an App
Open an App's files and type, or describe the change in a sentence — every proposed change is shown as a before/after per file, nothing is applied until you accept, and one click rolls it back.
An App keeps its project files in its own branch — a named list of path → contents that
lives inside the runtime, not on your disk. Editing an App means changing that branch. There
are two ways to do it, and both end at the same place.
- You type. Open a file, change it, save.
- You describe. Write one sentence about what you want changed, pick the files it touches, and the agent proposes new contents for each one.
The second way is the one that needs a safety net, so that is where this page starts.
Nothing applies until you accept
When you describe a change, the runtime does not write it. It runs an edit pass over each file you selected and returns a proposal: the file's current contents and the proposed contents, side by side, as a diff — one diff per file.
Describe the change. In the console, open the App and use the Modify app action in the
App header (the chat icon, next to run and the lock). Type the instruction in plain language —
for example, rename the widget to Gadget across the project and update its docs.
Pick the files in scope. Tick every file the change touches. Each selected file is rewritten in its own pass, and every other selected file is attached as read-only context to that pass, so the rewrites stay consistent with one another rather than drifting apart.
Review the diffs. One review gate opens with a current → proposed diff for each file that actually changed. A file the agent left byte-identical is dropped from the review — it is not a change, so it is not shown, not applied, and not rolled back.
Approve, or reject. Reject discards the whole proposal and returns you to the instruction box; the proposed contents were only ever stored as unreferenced blobs, so nothing in the App moved. Approve re-points each changed file to its proposed contents, one file after another.
Roll back if it was wrong. After approving, the drawer shows what was applied and a Roll back button. Rolling back re-points every file it touched to the exact contents it had before you approved.
Why rollback works
Contents are addressed by their hash, and applying a change does not delete the old contents — it only changes which contents a path points at. The previous version is still there, so a rollback is a second pointer move back. There is no separate history to enable.
Two honest limits
Approve applies the files one after another, not as a single all-or-nothing transaction. If one file fails to apply, the earlier ones are already applied and the error is shown — check the files and re-run. And the roll-back button belongs to that review session: once you press Done or close the drawer, restoring an earlier version means editing the file again.
Describing a change needs a model to be served, because a model is what writes the proposal. Reading files, typing edits yourself, locking, and unlocking all work with no model at all.
Typing an edit yourself
In the console's Files tab, select a file to view it. Edit opens the editor in place; Save stays disabled until you have actually changed something, and Cancel drops the edit. The same file also offers the agentic review path, so you can start by hand and finish by describing, or the reverse.
From the terminal, editing is a read-then-write pair. kx app cat prints the file, and
kx app edit replaces it from a local file.
kx app files apps/local/my-app
kx app cat apps/local/my-app README.mdkx app files lists the branch — every path, its content reference, and the count.
kx app cat prints one file's body to your terminal, or writes it to disk:
kx app cat apps/local/my-app README.md --out ./README.mdEdit that local copy in whatever editor you like, then push it back:
kx app edit apps/local/my-app README.md --from ./README.mdThe CLI never reads your project directory
kx app edit reads exactly one file — the one you named with --from — and stores its bytes as
the new body for that path in the App's branch. Nothing else on your machine is read, and
nothing on your machine is written except by --out. The path inside the App and the local
filename do not have to match.
kx app edit writes straight through with no review gate. The propose → diff → approve gate
lives in the console and in the SDKs; use it when you want to see the change before it lands.
Reading the App's shape
Files are one half of an App. The other half is its structure — the steps the App runs and the edges between them. Print it with:
kx app structure apps/local/my-appYou get a line per step (its kind, and its model route, tools or turn budgets when it has them)
and a line per edge. For the raw structure as stored, add --json:
kx app structure apps/local/my-app --jsonThe console shows the same thing as a diagram in the Lineage tab. Both are read-only views — they tell you what the App is, they do not change it. Structure is authored where the App is built; see Apps.
Lock it when you are done
An App that works is worth freezing. A lock is a policy switch on the App's project branch, set by you, that refuses writes:
kx app lock apps/local/my-appPrints app apps/local/my-app locked. While the lock is on, the runtime refuses:
- file edits — any change to a file in the App's branch, whether it came from the console, the CLI, an SDK, or the agent;
- structure edits — re-saving the App's envelope, so the shape cannot be changed either;
- scaffolding — the agent cannot write a fresh project tree over it.
Refusals come back as a failed precondition carrying the code LOCKED_BRANCH, so a tool can act
on the code rather than on the wording. The console checks the lock first and shows a notice in
place of the editing controls, rather than offering you a control that will fail.
Running the App is unaffected. A lock stops the App from being changed, not from being used.
kx app unlock apps/local/my-appPrints app apps/local/my-app unlocked. Both commands are idempotent — locking a locked App and
unlocking an unlocked one both succeed and leave you in the state you asked for.
What a lock is not
A lock is yours alone: it is stored against your identity and the App's handle, so you can only lock, unlock, and see your own Apps. It is an availability switch, not a security control or an integrity guarantee — the lock record lives outside the App's permanent history, and if that record is lost or rebuilt, branches read as unlocked and editing is restored. That is the deliberate direction: losing the record can never leave you unable to edit your own App. Do not treat a lock as tamper-proofing.
The same gate from code
The propose → review → approve sequence is three calls, so you can build your own review step. Read the branch first to capture each file's current reference — that reference is your rollback target.
The CLI has no propose/approve pair — kx app edit writes directly. To review before applying,
pull the file, diff it locally with your own tools, and push it back only if you like it.
kx app cat apps/local/my-app README.md --out ./before.md
cp ./before.md ./after.md
# edit ./after.md, then:
diff -u ./before.md ./after.md
kx app edit apps/local/my-app README.md --from ./after.mdTo undo, push ./before.md back the same way.
from kortecx import KxClient
with KxClient("http://127.0.0.1:50151") as kx:
handle = "apps/local/my-app"
branch = kx.get_branch(handle)
if branch is None:
raise SystemExit("no project branch for this app")
prior = {it.path: it.content_ref for it in branch.items}
# PROPOSE — runs the edit pass, advances nothing.
proposal = kx.edit_branch_propose(handle, "README.md", "add a short usage section")
print(proposal.current_text)
print(proposal.proposed_text)
# APPROVE — re-point the path at the proposed contents.
kx.advance_branch(handle, "README.md", proposal.result_ref)
# ROLL BACK — re-point it at what it was before.
kx.advance_branch(handle, "README.md", prior["README.md"])import { KxClient } from "@kortecx/sdk/node";
const kx = new KxClient("http://127.0.0.1:50151");
const handle = "apps/local/my-app";
const branch = await kx.getBranch(handle);
if (branch === null) throw new Error("no project branch for this app");
const prior = new Map(branch.items.map((it) => [it.path, it.contentRef]));
// PROPOSE — one instruction, other files attached so the rewrite stays coherent.
const p = await kx.editBranchPropose(handle, "README.md", "add a short usage section", {
contextPaths: ["README.md", "config.json"],
});
console.log(p.currentText, p.proposedText);
// APPROVE, then ROLL BACK.
await kx.advanceBranch(handle, "README.md", p.resultRef);
await kx.advanceBranch(handle, "README.md", prior.get("README.md")!);A proposal that the model returns empty is refused rather than applied, so a model that answers with nothing cannot blank a file.
Getting a gateway up
Every command on this page talks to a running gateway. The bare serve command refuses to
start without an explicit stance on authentication, which is deliberate:
kx serve --dev-allow-localThat is the local development stance, and it accepts loopback connections only. For anything else, start it with an auth token instead. See Install and Serving.
Next
Hosted Web Apps
Describe a small web tool and the agentic runtime builds it, installs it, and runs it on your own machine so you can open it in a browser.
Sharing an App
Export a Kortecx App to a single portable .kxapp file, import it under your own account, or clone it locally — and understand exactly what travels and what never does.