Skip to content

ctx_push

ctx_push sends the local .contexo/ pages to the team server. It is the tool with the most behaviour behind it: one call can come back as a plain result, as a PUSH_PAUSED handshake asking the agent to write the reasoning trail first, or as a MERGE_REQUIRED three-way merge. Both of those are agent directives — instruction blocks the agent is expected to act on and then call again.

From the tool description: “Use when the user says something like ‘sync my stripe knowledge to contexthub’ or ‘share this with the team’.” Anything that reads as “publish what we just figured out” lands here. Filters let the agent push a subset rather than the whole store — feature (an alias for tag), tag, or type.

ParameterTypeRequiredDescription
distill_donebooleannoSet true after authoring the source page that the previous PUSH_PAUSED response requested
featurestringnoTag to filter by (e.g. 'stripe')
messagestringnoCommit message
no_distillbooleannoSkip the reasoning-trail handshake for this push
source_slugstringnoSlug of the source page just written (required when distill_done=true)
tagstringnoAlias of feature
typeconcept | entity | source | analysisno

feature and tag are the same filter; feature wins if both are set. Both match tags case-insensitively. type filters on the page type. With no arguments, every page in the store is pushed.

  1. Lists the store, applies the filters. No match → Nothing to push (no pages match filters).
  2. Serializes each page and pairs it with its parent_sha from .contexo/.sync/state.json (page_shas). A page with no entry there is sent with no parent_sha at all, and the server skips its conflict check for that file — it is written whether or not the path already exists there.
  3. POSTs the batch with the author name/email from stored credentials and message (default: agent push (N pages)).
  4. Records the returned sha for each accepted file back into page_shas.

The server writes and commits one file per commit, all sharing the push message, so pushed[] carries a different sha per page and the reported HEAD is the last one. A file whose content is byte-identical to what is already there produces no commit at all — pushing the whole store repeatedly is cheap and does not create empty commits.

{
"jsonrpc": "2.0",
"id": 11,
"method": "tools/call",
"params": {
"name": "ctx_push",
"arguments": {
"feature": "stripe",
"message": "stripe trials: subscription-level trial_end"
}
}
}
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"content": [{ "type": "text", "text": "Pushed 2 page(s); HEAD=a4b2c1d3" }]
}
}

The HEAD in the text is shortened to 8 characters. Everything this tool returns — success, pause, merge, error — arrives as that single text block; the agent reads it and decides what to do next.

A push that would ship knowledge pages without the reasoning behind them pauses instead. The tool returns <PUSH_PAUSED reason=distill_required>, and it fires only when all of these hold:

  • the batch contains at least one concept or analysis page, and
  • a capture buffer for this project was modified within the last 6 hours and has at least one recorded turn, and
  • no_distill is not set, and
  • the environment variable CONTEXO_DISTILL_DISABLE is not 1.

Entity-only or source-only batches never trigger it. Neither does a project with no capture hooks installed — nothing has been recorded, so there is nothing to distill.

The directive asks for three steps, inlines the session buffer so the agent has the raw material, and suggests a slug of the form YYYY-MM-DD-<first-concept-slug>-reasoning:

<PUSH_PAUSED reason=distill_required>
Before pushing: capture the reasoning trail, then make sure every named system
in this batch has its own entity page. Do the three steps below in order, then
re-invoke ctx_push with distill_done=true.
STEP 1 — call ctx_write_page with:
type: "source"
slug: "2026-07-28-stripe-subscription-reasoning" (suggested; adjust topic to taste)
tags: [stripe billing] (union of the batch's tags)
reasoning_summary: one-line distillation (<= 100 chars)
body: follow the TEMPLATE below
TEMPLATE (drop sections that genuinely don't apply, keep them in this order):
## Decision
## Why this approach
## Rejected alternatives
## Path of inquiry
## Dead-ends
## Open questions
## Sources
[each section carries a one-line instruction in the real directive]
IMPORTANT: redact any API keys, tokens, passwords, or PII you encounter.
STEP 2 — entity coverage: if the concept/analysis pages reference named
systems, services, libraries, products, or databases that don't already have
a page under wiki/entities/, create one for each with ctx_write_page(type="entity").
Keep entity pages short: purpose, where it lives in this project, gotchas, and
links to related concepts. Skip this step if every named thing is already covered.
STEP 3 — call ctx_push again with the same filter args plus:
distill_done: true
source_slug: "2026-07-28-stripe-subscription-reasoning"
That distill_done call will link the source into each concept/analysis page's
`sources:` frontmatter, archive the buffer, and push everything in one commit.
---
PAGES being pushed in this batch:
- wiki/concepts/stripe-subscription.md (concept)
BUFFER (turn-by-turn summaries from this session, oldest first):
[one JSON object per turn, oldest first; long sessions keep the first 10
and last 40 turns with a truncation marker between them]
</PUSH_PAUSED>

The second call must carry both distill_done: true and source_slug, plus the same filters as the first. source_slug is the slug of the page just written with type: "source" — the tool reads raw/sessions/<source_slug>.md and refuses the push if it is not there:

{
"jsonrpc": "2.0",
"id": 13,
"method": "tools/call",
"params": {
"name": "ctx_push",
"arguments": {
"feature": "stripe",
"distill_done": true,
"source_slug": "2026-07-28-stripe-subscription-reasoning"
}
}
}

That call then, before sending anything:

  • appends source_slug to the sources: frontmatter of every concept and analysis page in the batch, bumps their updated, and rewrites them on disk;
  • adds the source page itself to the batch if the filters didn’t already include it;
  • archives the capture buffer (moves it to raw/sessions/_pending/_archive/) so the same session can’t trigger the handshake twice.

So the reasoning page and the pages it explains go up together, cross-linked.

Set no_distill: true to skip the handshake for one push — that is what ctx_migrate and ctx_generate instruct the agent to do, since imported or code-derived pages have no session reasoning to capture.

If any file in the batch has moved on the server since your last pull, that file’s write is rejected (HTTP 409) and the tool returns <MERGE_REQUIRED> instead of a bare error. Files in the same batch that did not conflict are still committed.

The directive carries, per conflicting file: the ancestor sha both sides diverged from, the server’s current sha, a per-section summary of what each side changed, the sections modified by both sides, and all three full versions.

<MERGE_REQUIRED>
Push rejected: 1 file(s) have moved on the server since your edit.
Reconcile each file below by writing a merged version that
incorporates BOTH your changes AND the server's changes, then
re-invoke ctx_push. The state has been updated so the re-push
uses the server's current sha as the new parent.
─── FILE 1/1: wiki/concepts/stripe-subscription.md ───
ancestor (both diverged from): a4b2c1d
server now: 9f31c07
YOUR changes vs ancestor:
~ frontmatter changed
~ ## Trials
SERVER changes vs ancestor:
~ ## Trials
+ ## Refund handling
CONFLICTING regions (both sides changed):
## Trials — synthesize both edits into one coherent version
--- ANCESTOR VERSION ---
[full page bytes]
--- YOUR VERSION (the one you tried to push) ---
[full page bytes]
--- SERVER VERSION ---
[full page bytes]
STEPS for the agent:
1. For each file above: write a merged version via
ctx_write_page(slug=..., type=..., body=<merged content>)
The merged page must preserve BOTH sides' intent. For prose
sections that conflict, synthesize — don't pick one.
2. Re-invoke ctx_push with the SAME filters. The local sync state
has been updated to point at the server's current sha for each
conflicting file, so the next push will not 409 unless someone
pushed again in the meantime.
</MERGE_REQUIRED>

When no section was touched by both sides, that block is replaced by (No section was modified by both sides — auto-mergeable in principle.) — the agent still has to write the merged page, but it can take both sides wholesale.

Drift and conflicts walks through the whole cycle, including the <DRIFT_NOTICE> that ctx://wiki/<slug> reads prepend so the agent learns about divergence before it edits.

Text Cause
ctx_push: server not configured (run 'ctx remote set <url>', 'ctx remote set-repo <id>', 'ctx auth login') No credentials, server URL, or repo id
ctx_push: distill_done=true requires source_slug (…) distill_done set without naming the source page
ctx_push: distill_done set but source page "…" not found at raw/sessions/….md The source page was never written, or its slug differs
Nothing to push (no pages match filters) Filters matched zero pages (not an error result)

ctx push pushes the same batch with the same filters.

ctx push --yes
About to push 1 page(s) to acme-api:
  [NEW]   wiki/concepts/stripe-subscription.md
Pushed 1 page(s); HEAD=a4b2c1d3

Editing a page that already exists shows a per-section summary of what will change, and in a non-interactive shell requires --yes to proceed:

ctx push --yes (page already on the server)
About to push 1 page(s) to acme-api:
  [EDIT]  wiki/concepts/stripe-subscription.md  (frontmatter, ~ Concepts, + Webhooks, - Open Questions)
Pushed 1 page(s); HEAD=b7e9f204
ctx push --dry-run
About to push 1 page(s) to acme-api:
  [NEW]   wiki/concepts/stripe-subscription.md
(dry-run; no changes sent)
FlagTypeDefaultDescription
--dry-runboolfalseshow what would be pushed without sending
--fallback-serverboolfalse(planned) route reasoning-trail distillation to the Contexo server; currently errors
--featurestringpush only pages tagged with this feature/tag
--message, -mstringcommit message (default: 'ctx push (N pages)')
--no-previewboolfalseskip the pre-push preview entirely (faster; loses the heads-up about what changes)
--show-diffboolfalseprint the full per-section diff in the preview, not just a one-line summary
--tagstringpush only pages with this tag (alias of --feature)
--typestringpush only pages of this type (concept|entity|source|analysis)
--yes, -yboolfalseskip the pre-push confirmation prompt (required for non-interactive use when editing existing pages)

Two differences from the MCP tool, both deliberate:

  • No distill handshake. The agent-as-distiller flow only exists over MCP; ctx push never pauses to ask for a reasoning page. (--fallback-server is reserved for a server-side distiller and currently errors.)
  • No merge directive. On 409 the CLI prints the conflicting paths and tells you to ctx pull, merge by hand, and push again; it does not rewrite your sync state.

See ctx push for the full command reference.