Scenarios and usage guidelines
The quickstart shows the shortest path: capture, send,
receive. This page shows why you'd run those (and the rest of the CLI), in
three realistic situations, plus the guidance to decide when Ophiuchus is (and
isn't) the right tool.
Ophiuchus ships ten commands: version, status, doctor, alpha-token-new,
capture, inspect, export, send, receive, history. The scenarios
below use most of them; run ophiuchus --help for the full list. There is no
feature behind any of these scenarios that isn't one of those ten commands —
only the situation and the fields you fill in change.
The rediscovery-avoidance benefit these scenarios describe is not a measured result. It's the thing Ophiuchus's dogfooding is trying to find out, based on one real handoff so far. See the overview's note on this before treating any scenario below as a guaranteed time savings.
capture accepts an optional contacts field (see Scenario A) — a bounded,
sender-supplied "who to ask" pointer rendered clearly as unverified data,
never as a confirmed identity. That is the entire feature. There is no
persistent contact list, address book, org directory, or "who owns this"
lookup anywhere in Ophiuchus, and no code path that infers, resolves, or
verifies a contact_ref against anything — it's whatever string the sender
typed (@bob, #incident-channel, an email address), rendered back exactly
as given. If a capsule needs a contact and the sender didn't supply one, there
is no fallback lookup — the field is simply absent.
Scenario A — cross-user, cross-tool handoff
Alice is debugging a token-refresh bug with Claude Code on her laptop. She's found the real cause, ruled out a theory that looked plausible, and is handing the fix to Bob, who picks it up with Codex on a different machine.
Precondition: something both machines can reach over HTTPS is running the
relay. The quickstart's loopback relay only works for one machine talking to
itself — for a real Alice-and-Bob handoff, one of you stands up the relay
(uv run python -c "from ophiuchus.transport.relay import run; run()", put
your own TLS in front of it) and shares that URL with the other, out of band.
1. Alice captures. She knows Bob may still have questions once he's read her evidence, so she includes a Contact Card pointing at herself — optional, and just plain text she typed:
echo '{
"goal": "make the token refresh survive a clock skew",
"state": "reproduced the failure; the expiry is compared in local time",
"next_action": "switch the expiry comparison to UTC and add a regression test",
"rejected": ["the network-timeout theory - the request never leaves the client"],
"evidence": ["test_refresh_after_skew fails only when TZ=America/Chicago"],
"constraints": ["cannot change the token format - other services already parse it"],
"open_questions": ["does the mobile client hit the same comparison, or a separate one?"],
"contacts": [
{"display_name": "Alice", "role": "Original reporter", "preferred_channel": "github", "contact_ref": "@alice"}
]
}' | uv run ophiuchus capture --name handoff --out-dir .ophiuchus \
--repo /path/to/the/work/repo \
--producer-version "$(claude --version)"
This writes .ophiuchus/handoff.capsule.json (the wire format) and
.ophiuchus/handoff.capsule.md (Alice's own readable copy — Bob does not need
this file).
1a. Alice previews it before sending (optional but recommended).
inspect renders exactly what's about to leave the machine — including the
Contact Card and the result of capture's deterministic quality checks
(missing evidence, missing next action, that kind of thing) — without
transmitting anything:
uv run ophiuchus inspect .ophiuchus/handoff.capsule.json
send --preview is a second way to see the same rendering without a separate
inspect call — it still exits without transmitting anything, so you'd run
plain send again afterward to actually send.
2. Alice sends it.
uv run ophiuchus send .ophiuchus/handoff.capsule.json --relay-url https://the-shared-relay
# location_id: bb0f870f…
# expires_at: 2026-09-06T21:15:09+00:00
# token: HNwRSQs8…
3. Exactly what Alice gives Bob. Two values, over two different channels, because they have different sensitivity:
location_id— a locator, not a secret. Fine in Slack, email, a Jira comment.token— a secret. Anyone holding it can fetch the capsule until it expires or is consumed. Send it the way you'd send a password: a DM, not a public channel, not a commit message.
Nothing else. Not a file, not a URL to click, not a repo invite.
4. Bob receives it.
export OPHIUCHUS_CAPABILITY_TOKEN=…the token Alice sent…
uv run ophiuchus receive --location-id bb0f870f… --name handoff --with codex \
--relay-url https://the-shared-relay
5. What Bob should see. handoff.capsule.md rendered as a Codex-ready
document: Alice's goal/state/next_action quoted as her claims, the
rejected theory called out as already-attempted, Alice's Contact Card
rendered as sender-supplied — not a verified identity — in case Bob's evidence
review still leaves something only Alice can answer, and a freshness line
comparing the capsule's grounding commit against Bob's own checkout.
6. Afterwards: ophiuchus history and ophiuchus export. Once the fetch
completes, ophiuchus history lists it on both machines — a low-sensitivity
local record (who sent/received, when, what status) that never stores the
capsule body, the token, or the Contact Card value itself. If either of them
wants a durable copy to paste into a PR description or a Jira comment,
ophiuchus export handoff.capsule.json produces the same Markdown rendering
as a file, byte-identical to what capture already wrote — it never talks to
the relay again.
How Bob verifies he avoided rediscovery: before touching the code, Bob reads
the rendered rejected section. If his first instinct was "maybe it's a network
timeout," the document already tells him that's closed — he doesn't have to
re-run an investigation Alice already closed off to find that out himself.
Whether that reliably happens, and how much it's worth when it does, is exactly
the open question noted above — watch for it happening or not happening rather
than assuming it will.
TTL / delete-after-read: send --delete-after-read makes Bob's fetch the
last one possible — a second receive with the same token 404s exactly like an
expired one would. Without that flag, the capsule stays fetchable by anyone
holding the token until expires_at.
Scenario B — handing a fix to a different repo
Same mechanism as Scenario A. What's different: Alice's investigation happened in her repo, but the fix belongs in a repo she doesn't own.
The only thing that changes is which repo --repo points at: point it at the
repo the fix belongs in, not the one Alice was debugging in. Ophiuchus does not
know these are "different teams" or "different owners" — it has no concept of
ownership at all. The organizational boundary Alice is crossing is real; the
tool crossing it is exactly the same capture/send/receive triple, grounded
against whichever checkout is relevant to the recipient's next action:
echo '{
"goal": "payments service 500s on a specific webhook payload shape",
"state": "confirmed in api-gateway logs; the malformed field originates in payments-service's webhook validator, not the gateway",
"next_action": "add validation for the missing optional field in payments-service's webhook handler",
"evidence": ["gateway access log shows the 500 originates downstream, not at the gateway"],
"rejected": ["gateway timeout theory - the gateway forwards successfully; payments-service returns the 500"],
"constraints": ["cannot change the webhook payload shape - it is set by the external provider"],
"open_questions": ["does payments-service already validate this field elsewhere and just miss this path?"]
}' | uv run ophiuchus capture --name webhook-500 --out-dir .ophiuchus \
--repo /path/to/payments-service \
--producer-version "$(claude --version)"
--repo here is payments-service's checkout — not api-gateway, where Alice
actually found the bug — because that's the repo the receiving engineer's
freshness check needs to be grounded against. Send and receive proceed exactly
as in Scenario A; the receiving engineer gets Alice's evidence and the ruled-out
gateway theory without having to independently re-diagnose which service the
bug is actually in.
Scenario C — interrupted work, resumed later
You (one person, one investigation) stop for the day, or switch from Claude Code to Codex on the same task without a live conversation window to hand off from.
What Ophiuchus preserves: exactly what you put in capture — goal,
state, next_action, and whichever of evidence/rejected/constraints/
open_questions you filled in — plus the commit your checkout was on when you
captured it.
What it does not preserve: anything you didn't write into those fields. Not your shell history, not the files you opened, not the tool calls Claude Code made, not a transcript. If it mattered enough to matter tomorrow, it has to be a sentence in the capsule.
# before stopping:
uv run ophiuchus send .ophiuchus/investigation.capsule.json
# note the location_id and token somewhere you'll have tomorrow
# tomorrow, same or different machine:
export OPHIUCHUS_CAPABILITY_TOKEN=…
uv run ophiuchus receive --location-id … --name investigation --with codex
How to verify the continuation is actually useful: open the rendered
Markdown and check the freshness banner first. If your checkout has moved past
the grounding commit (you or someone else committed something in the meantime),
Ophiuchus says so loudly rather than pretending the context is still current —
that banner, not the presence of the file, is what tells you whether to trust
next_action as-is or re-verify state before continuing.
This is the same thing the product's internal experiment calls Time-to-Useful-Continuation: how fast you get back to doing correct work, not just to having a file open again.
Scenarios D–F: fidelity, validity and continuity
Scenarios A–C cover the base fields. The three scenarios below exercise the
optional critical, validity, negative_knowledge and --parent fields
covered in Fidelity, validity, negative knowledge and
continuity — each one is a real, runnable sequence against
the loopback relay, not a hypothetical. Every command below was run against
v0.0.0 before this page was written; the exact output shown is what that
run printed.
Shared precondition for all three: the loopback relay running in its own terminal (quickstart step 2), and every command run from your Ophiuchus checkout.
Scenario D — a critical constraint and a rejected hypothesis survive a handoff
A predecessor marks its rejected hypothesis as must-preserve, sends it, and a successor's rendering shows it came through.
1. Capture, with a Critical Context Unit pointing at rejected[0]:
echo '{
"goal": "stop the nightly inventory-reconciliation job from double-counting returned units after a partial retry",
"state": "reproduced against the fixture warehouse dataset: the retry path re-reads the batch from its start offset instead of resuming from the last committed checkpoint",
"next_action": "change the retry path to resume from the persisted checkpoint offset, then rerun test_partial_retry_no_double_count",
"evidence": ["test_partial_retry_no_double_count FAILED - expected on_hand=142, got on_hand=178 for SKU-40921 after simulated timeout+retry"],
"rejected": ["the queue-redelivery theory - ruled out because the broker'\''s delivery-count metric stays at 1 for every event in the reproduction, so nothing is being redelivered"],
"critical": [
{"unit_id": "cu-rejected-0", "kind": "rejected", "index": 0, "required": true}
]
}' | uv run ophiuchus capture --name handoff-d --out-dir .ophiuchus --producer-version demo
Expected intermediate state: inspect .ophiuchus/handoff-d.capsule.json
shows a ## Critical context units section with cu-rejected-0 — rejected[0] (required). This is the sender-side preview only — the verdict itself is
still unknown here, because no consumer path has evaluated it yet.
2. Send and receive (same machine is fine for this check):
uv run ophiuchus send .ophiuchus/handoff-d.capsule.json
# note location_id and token
export OPHIUCHUS_CAPABILITY_TOKEN=<token>
uv run ophiuchus receive --location-id <location_id> --name handoff-d --with codex
Final observable verification: the rendered ## Context fidelity section
in handoff-d.capsule.md reads:
- `cu-rejected-0` (rejected[0], required): preserved — carried through to
this consumer's rendering.
inspect cannot show this verdict at all, with or without --repo:
inspect takes only a capsule file and --repo (which affects validity
— freshness against a checkout — not fidelity), and never renders against a
consumer adapter. Running ophiuchus inspect .ophiuchus/handoff-d.capsule.json --repo <recipient's checkout> prints
"Declared by the sender. No consumer path has been evaluated" for the
critical unit regardless of --repo. A fidelity verdict is only ever
computed against a specific consumer's rendering (see
ophiuchus.capsule.fidelity), which only receive --with codex (used
above) performs — there is no local-only shortcut for this particular
check.
Cleanup: delete .ophiuchus/handoff-d.capsule.* (or rm -rf .ophiuchus)
— capsule files under .ophiuchus/ are gitignored, so this is local-only
cleanup, not a repository change.
Scenario E — a handoff arrives after the world moved on
A predecessor declares a validity condition. By the time the recipient reads
it, the declared condition has lapsed — the successor sees a loud stale
verdict rather than silently trusting old context.
1. Capture, with a time_window condition two seconds out and a
declared_scope condition (always unverifiable):
NOT_AFTER="$(python3 -c 'import datetime; print((datetime.datetime.now(datetime.UTC)+datetime.timedelta(seconds=2)).isoformat())')"
echo "{
\"goal\": \"fix the nightly inventory-reconciliation retry offset bug\",
\"state\": \"root cause confirmed in the retry path'\''s checkpoint handling\",
\"next_action\": \"apply the checkpoint-resume fix and rerun the regression test\",
\"validity\": [
{\"condition_id\": \"vc-window\", \"kind\": \"time_window\", \"not_after\": \"$NOT_AFTER\", \"required\": true},
{\"condition_id\": \"vc-scope\", \"kind\": \"declared_scope\", \"subject\": \"the demo fixture only\", \"required\": true}
]
}" | uv run ophiuchus capture --name handoff-e --out-dir .ophiuchus --producer-version demo
Expected intermediate state: run ophiuchus inspect .ophiuchus/handoff-e.capsule.json immediately — vc-window reads valid
("before its deadline …"), vc-scope reads unverifiable (it always does).
2. Wait for the deadline to pass (3 seconds is enough), then send and receive:
sleep 3
uv run ophiuchus send .ophiuchus/handoff-e.capsule.json
# note location_id and token
export OPHIUCHUS_CAPABILITY_TOKEN=<token>
uv run ophiuchus receive --location-id <location_id> --name handoff-e --with codex
Final observable verification: receive prints this on stderr, and the
same line appears at the top of handoff-e.capsule.md:
warning: CONTEXT VALIDITY — partially_stale (2 required condition(s) not
valid; ids: `vc-window, vc-scope`). Validity compares the sender's declared
conditions against this machine's checkout only. It is not a trust,
authority or correctness judgement, and it verifies no system outside this
repository.
The aggregate is partially_stale, not stale — one required condition
(vc-window) is concretely stale and the other (vc-scope) is
unverifiable; per the aggregation rule described in
Semantics, a stale result mixed with anything else
outranks an unrelated unverifiable rather than the two averaging out or
unverifiable winning.
Both per-condition verdicts are visible individually under ## Context validity in the same file. A capsule declaring only a git_paths condition
against a file you then edit in your working tree (uncommitted is enough)
demonstrates the same stale/partially_stale mechanism grounded in a real
repository change instead of a clock — revert the edit afterward
(git checkout -- <path>) if you try this against a tracked file.
Cleanup: delete .ophiuchus/handoff-e.capsule.*.
Scenario F — a second handoff supersedes evidence and revalidates a rejected path
A predecessor rejects a theory, conditioned on a validity window. That window
lapses before the theory is revisited. A successor capsule declares the first
one as its parent, marks the negative-knowledge item revalidated, and
supersedes a critical unit — inspect on the successor shows exactly what
changed.
1. Capture the parent capsule. Omit --name so the file is written as
<capsule_id>.capsule.json — this matters: inspect's continuity walk looks
for a sibling file named after the declared parent_capsule_id, in the same
directory as the capsule being inspected, so the child capsule must be
captured into the same --out-dir as the parent, and the parent must keep
its capsule-id filename.
NOT_AFTER="$(python3 -c 'import datetime; print((datetime.datetime.now(datetime.UTC)+datetime.timedelta(seconds=2)).isoformat())')"
echo "{
\"goal\": \"fix the nightly inventory-reconciliation retry offset bug\",
\"state\": \"root cause narrowed to the retry path'\''s checkpoint handling\",
\"next_action\": \"confirm the checkpoint offset is honored on retry\",
\"evidence\": [\"reconciliation.log line 2201: resuming batch b-2026-09-01-03 at offset 0 (expected 8400)\"],
\"critical\": [
{\"unit_id\": \"cu-p-next-action\", \"kind\": \"next_action\", \"required\": true}
],
\"validity\": [
{\"condition_id\": \"vc-window\", \"kind\": \"time_window\", \"not_after\": \"$NOT_AFTER\", \"required\": true}
],
\"negative_knowledge\": [
{
\"item_id\": \"nk-queue-redelivery\",
\"hypothesis\": \"the queue is redelivering duplicate events; add an idempotency key at the consumer\",
\"why_invalid\": \"the broker'\''s delivery-count metric stays at 1 for every event in the reproduction, so nothing is being redelivered\",
\"state\": \"rejected\",
\"evidence_refs\": [0],
\"applies_while\": [\"vc-window\"],
\"retry_if\": \"revisit if the broker'\''s delivery-count metric ever moves off 1 during a future reproduction\"
}
]
}" | uv run ophiuchus capture --out-dir .ophiuchus/chain --producer-version demo-parent
# wrote .ophiuchus/chain/<parent-id>.capsule.json — note <parent-id>
2. Wait for the window to lapse, then confirm the parent alone now needs revalidation:
sleep 3
uv run ophiuchus inspect .ophiuchus/chain/<parent-id>.capsule.json
Expected intermediate state: the ## Negative knowledge section reads:
- `nk-queue-redelivery` (`rejected`): revalidation_required — at least one
applicability condition is stale or partially stale — the assumption this
rejection depended on may no longer hold.
3. Capture the child, declaring the parent and revalidating the item:
NOT_AFTER_1H="$(python3 -c 'import datetime; print((datetime.datetime.now(datetime.UTC)+datetime.timedelta(hours=1)).isoformat())')"
echo "{
\"goal\": \"fix the nightly inventory-reconciliation retry offset bug\",
\"state\": \"checkpoint-resume patch verified on staging; re-checked the queue-redelivery theory after the earlier window lapsed and it still does not explain the double-count\",
\"next_action\": \"ship the checkpoint-resume fix and add the regression test\",
\"evidence\": [\"reconciliation.log line 2201: resuming batch b-2026-09-01-03 at offset 0 (expected 8400)\", \"staging soak: delivery-count metric held at 1 throughout; duplicate-count bug persisted regardless\"],
\"critical\": [
{\"unit_id\": \"cu-c-next-action\", \"kind\": \"next_action\", \"required\": true, \"supersedes\": \"cu-p-next-action\"}
],
\"validity\": [
{\"condition_id\": \"vc-window\", \"kind\": \"time_window\", \"not_after\": \"$NOT_AFTER_1H\", \"required\": true}
],
\"negative_knowledge\": [
{
\"item_id\": \"nk-queue-redelivery\",
\"hypothesis\": \"the queue is redelivering duplicate events; add an idempotency key at the consumer\",
\"why_invalid\": \"re-checked after the parent'\''s applicability window lapsed: the delivery-count metric held at 1 throughout a staging soak and the double-count bug persisted regardless, so redelivery still is not the cause\",
\"state\": \"revalidated\",
\"evidence_refs\": [1],
\"applies_while\": [\"vc-window\"]
}
]
}" | uv run ophiuchus capture --out-dir .ophiuchus/chain --producer-version demo-child --parent <parent-id>
# wrote .ophiuchus/chain/<child-id>.capsule.json
Final observable verification:
uv run ophiuchus inspect .ophiuchus/chain/<child-id>.capsule.json
prints a ## Continuity section:
Parent: `<parent-id>`
Chain: 1 ancestor(s) observed, termination: complete
- depth 1 `<parent-id>`: present — loaded from disk
- critical: inherited 0, added 1, dropped 1
- validity: inherited 1, added 0, dropped 0
- negative_knowledge: inherited 1, added 0, dropped 0
- rejected: carried_over 0, added 0, absent_here 0
- evidence: carried_over 1, added 1, absent_here 0
- constraints: carried_over 0, added 0, absent_here 0
- open_questions: carried_over 0, added 0, absent_here 0
- `cu-c-next-action` supersedes `cu-p-next-action`: resolved=True
- `nk-queue-redelivery`: `rejected` → `revalidated`
resolved=True confirms the successor's supersedes pointer actually
resolved against a unit_id in the parent's critical list — the next_action
Critical Context Unit was explicitly replaced, not silently dropped. The
negative_knowledge transition line confirms the previously-rejected
nk-queue-redelivery theory was formally revalidated, not merely restated:
the child's own ## Negative knowledge section now shows it as retired
(state revalidated always reads retired, regardless of any applicability
condition), closing the loop the parent's revalidation_required opened.
Cleanup: rm -rf .ophiuchus/chain.
Scenario G — publishing a handoff as a Record or a Discussion
Scenarios A–F move a capsule between two Ophiuchus sessions. This scenario is
different: it materializes a verified handoff where your team already works
— a GitHub issue, a Jira ticket, a ClickUp task, a Slack message, a Teams
channel message — using ophiuchus publish (ADR-0025, the Workflow Handoff
Bridge). Ophiuchus remains the handoff layer here too: it is never the
system of record for GitHub/Jira/ClickUp/Slack/Teams, and it never reads
anything back from them. There is no bidirectional sync, no polling, no
webhook ingestion — see Semantics
for the full non-goal list.
ophiuchus publish takes the capsule you already captured (same file
capture wrote in Scenario A) and one of two profiles:
--profile record— a durable, management-friendly artifact: title, goal, current state, evidence, rejected hypotheses, constraints, requested next action. Destinations: GitHub (issues), Jira (issues/comments), ClickUp (tasks/comments).--profile discuss— a shorter, conversation-friendly artifact: summary, why discussion is needed, trimmed evidence, rejected hypotheses, requested discussion. Destinations: Slack (messages), Teams (channel messages).
--preview is mandatory to run first, every time, for every provider: it
composes the view and prints exactly what would leave this machine —
--to/provider is never looked up, no credential is read, and no network
call is made — so it is honestly side-effect-free regardless of destination.
Contact Cards are excluded unless you pass --include-contacts explicitly.
Credential setup (any provider): set the OPHIUCHUS_<PROVIDER>_*
environment variable(s) documented for that provider — never a literal
example token in a capsule, a command, or this page. The capability matrix
below names the exact credential/scope each provider needs.
This environment vs. yours: the commands below are real — they are the
literal ophiuchus publish invocations you'd run — and GitHub's has real
end-to-end evidence behind it (verified against a disposable sandbox repo,
Chisanan232/ophiuchus-publish-sandbox). Jira, ClickUp, Slack, and Teams
adapters are built and unit-tested, but this environment does not hold a
live credential for any of them, so a mutating publish call to one of
those four here would stop at a credential-missing error before any network
call, not because the command is wrong. Run them for real once you've set
the provider's credential in your own environment.
G1 — GitHub: create an issue from a RECORD view (real E2E evidence)
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile record --to github --target <OWNER>/<REPO> --preview
# prints the composed RECORD view: title, goal, current state, the rejected
# hypothesis, requested next action -- nothing sent yet
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile record --to github --target <OWNER>/<REPO>
# creates a new GitHub issue in <OWNER>/<REPO>; the printed outcome carries
# the issue's external reference
To comment on an existing issue instead of creating one, add
--operation append --target <OWNER>/<REPO>#<ISSUE_NUMBER>.
Afterwards: the Delivery Receipt sidecar (not ophiuchus history, which
only ever knows about relay sends/receives) now holds one row for this
publication — provider, operation, the GitHub issue reference, status
published, and the idempotency_key this call used. Deleting the local
capsule or history entry later does not delete the GitHub issue —
its lifecycle now belongs to GitHub (ADR-0025 §6).
G2 — Jira: create an issue from a RECORD view
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile record --to jira --target "<PROJECT_KEY>:Task" --preview
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile record --to jira --target "<PROJECT_KEY>:Task"
Jira's --target for --operation create (the default) is
"<PROJECT_KEY>:<IssueType>" (e.g. "HORO:Task") — a project key alone is
not enough, Jira always requires an issue type. For --operation append,
--target is an existing issue key (e.g. "HORO-1520").
G3 — Slack: post a discussion message from a DISCUSS view
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile discuss --to slack --target <CHANNEL_ID> --preview
uv run ophiuchus publish .ophiuchus/handoff.capsule.json \
--profile discuss --to slack --target <CHANNEL_ID>
To reply within an existing thread instead of starting a new message, use
--operation reply --target <CHANNEL_ID>:<THREAD_TS>.
Teams — honest current gate, not a fourth runnable scenario
Teams' --profile discuss --to teams adapter is built and unit-tested
(create a channel message / reply to one, against Microsoft Graph's real
POST /teams/{team_id}/channels/{channel_id}/messages and
.../messages/{message_id}/replies endpoints) but this environment has no
live Microsoft Teams tenant to publish against — there is no real E2E
evidence for Teams the way there is for GitHub. The exact credential it
needs, when you have a tenant to point it at: a Microsoft Graph access token
for an app or delegated identity granted the single scope
ChannelMessage.Send (never a broader scope like Directory.Read.All)
for the target team/channel, set via OPHIUCHUS_TEAMS_ACCESS_TOKEN. Until
that credential exists somewhere this CLI can reach, treat
ophiuchus publish ... --to teams as --preview-only in practice.
Capability matrix
Built from each adapter's own declared DestinationCapability
(src/ophiuchus/bridge/providers/*.py), not asserted from memory — a
provider that cannot honestly do something declares that absence rather than
claiming universal parity (ADR-0025 §3):
| Provider | Profile | Operations | Idempotency | Mention handling | Real E2E evidence in this environment |
|---|---|---|---|---|---|
| GitHub | record | create, append | client-managed | inert only (mentions are neutralized, never active) | Yes — Chisanan232/ophiuchus-publish-sandbox |
| Jira | record | create, append | client-managed | inert only | No — credential-gated Human Gate |
| ClickUp | record | create, append | client-managed | inert only | No — credential-gated Human Gate |
| Slack | discuss | create, reply | client-managed | inert only | No — credential-gated Human Gate |
| Teams | discuss | create, reply | unknown (not verified; no mitigation beyond never retrying internally) | inert only | No — credential-gated Human Gate, no live tenant |
No provider in this table is native idempotency today — none of the five
providers' APIs are confirmed to accept the idempotency_key this adapter
derives as a provider-native dedup key, so every one relies on the Delivery
Receipt sidecar's (idempotency_key -> external_reference) record to avoid
a duplicate external write on retry (ADR-0025 §5). mention_support is
inert_only for every provider, by binding default — none of these
adapters is authorized to let a capsule-derived @mention or <!channel>
trigger a real notification; that would require its own ticket and its own
security review (ADR-0025 §7).
Lifecycle asymmetry: deleting a local Ophiuchus Context/history entry never deletes the external GitHub issue, Jira ticket, ClickUp task, Slack message, or Teams message it produced. The external artifact's lifecycle is owned by its own provider, not by Ophiuchus (ADR-0025 §6).
Non-goals (binding, not an omission): no bidirectional sync, no polling external changes back into Ophiuchus, no webhook ingestion, no treating any of these five providers as a canonical Context store, and no Slack/Teams inbox or chat-client behavior — Ophiuchus posts one message and never reads the channel. See Semantics for the complete list.
What do I give the other person? (summary)
| You want to hand over... | Concrete artifact | Sensitivity |
|---|---|---|
| Everything needed to continue | location_id | Share freely — it's a locator, not a credential |
| Permission to fetch it | token (printed once by send) | Secret — private channel only, like a password |
| Nothing else | — | No file, no URL to click, no repo invite is needed |
Usage guidelines
When to use Ophiuchus vs. Slack/Jira/PR: use it when the thing crossing the
boundary is in-flight investigation state — a goal, ruled-out theories,
evidence — that would otherwise get lossily summarized by a human, or not
transferred at all. Use a PR when the artifact is the code change. Use Jira
when the artifact is a planned, scoped work item. Use Slack when it's a
synchronous question, not a structured handoff. These aren't mutually
exclusive — Scenario A's location_id is exactly the kind of thing you'd also
paste into a Slack message or a Jira comment.
What belongs in a capsule — least-sufficient context: goal, state, and
next_action always. evidence, rejected, constraints, and
open_questions only when they exist and matter to the next step. Don't paste
a full log — cite the one line that proves the point. A capsule that tries to
carry everything is the raw transcript this product exists to avoid sending.
Sensitive information: a sent capsule leaves your machine and sits on
whatever relay you pointed it at, unencrypted at rest by default. The relay
operator can read it. Never put credentials, secrets, or personal data into a
capsule field. capture runs an advisory local secret scan and refuses by
default if it trips; --allow-secrets overrides that and still reports every
finding, masked, to stderr — read Privacy and safe use before you
reach for that flag.
Evidence vs. raw transcript: evidence is a specific, citable fact — a
test name, a log line, a repro command's output — not a pasted conversation.
If you're tempted to paste the whole chat, that's the sign you should be
summarizing into state or evidence instead.
Rejected hypotheses / negative knowledge: the highest-value field on the whole capsule. Naming what you tried and ruled out is the one thing a normal handoff note almost always drops, and it's exactly what stops the recipient from re-walking a dead end you already closed.
Constraints and open questions: constraints is a boundary already decided
— the recipient must not cross it while continuing. open_questions is
something you didn't resolve — naming it explicitly is different from silently
omitting it, because an omitted question reads as answered and an open one
reads as still unknown.
Contact Cards: include one only when you already know who the recipient
should ask if the capsule alone isn't enough — a Contact Card is sender-supplied
data, not a directory lookup, so there's nothing to include if you don't
already know. Every field in it leaves your machine when the capsule is sent,
same as everything else in the payload; inspect shows you exactly what that
looks like before you send.
Preview before you send: ophiuchus inspect <capsule> (or send --preview) is the honest way to answer "what exactly am I about to transmit?"
without a network call — it renders the same content a recipient will see,
plus the deterministic quality checks (missing evidence, missing next action,
capsule looks otherwise thin) that capture itself doesn't block on.
Local history and durable export: ophiuchus history is a low-sensitivity
local log of your own sends/receives (who, when, what status) — it never
stores the capsule body, a capability token, or a Contact Card value, and it
does not talk to the relay, so it only knows what happened on this machine.
Clear it with ophiuchus history clear. ophiuchus export <capsule> --format markdown turns a capsule you already have locally into a durable Markdown
file — the same rendering capture already wrote, just retrievable again
later, for pasting into a PR, a Jira comment, or an incident doc. Neither
command changes what's stored on the relay or how long a capsule survives
there.
Troubleshooting: ophiuchus doctor is the first thing to run when
anything above doesn't behave as expected — it checks your install, config,
relay reachability, schema compatibility, and local write permissions in one
pass, and reports secrets only as present/absent, never their value.
TTL and lifecycle:
A capsule is temporary by construction. The relay enforces its own expiry
ceiling (one hour by default) regardless of what a longer-lived capsule claims
for itself — send warns you when the two disagree. --delete-after-read
makes the first successful fetch the last one. There is no CLI command to
delete a capsule early in this build; the relay's DELETE /capsules/{location_id} HTTP endpoint (same bearer token send printed) does
it directly — see the quickstart's deletion note.
Retry / expired / revoked behavior: a missing, expired, already-consumed,
or wrong-token fetch all return the same 404-shaped response, on purpose —
Ophiuchus does not tell an unauthorized caller why a capsule is unreachable,
only that it is. If receive fails this way, the right move is to ask the
sender to check whether it already expired or was consumed, and resend if so —
not to assume the token was mistyped.
When NOT to use Ophiuchus:
- Continuing your own work in the same session — you don't need a handoff tool to keep talking to the same agent.
- Durable team knowledge. A capsule expires,
historyonly knows this machine's own sends/receives, andexportproduces one file at a time — none of these add up to a knowledge base or a search index over past handoffs. - Finding out who owns something you don't already know. A Contact Card only carries what the sender already knew and chose to type; there is no lookup to fall back on.
- Anything that needs encryption-at-rest or a compliance guarantee — Ophiuchus doesn't provide either.
- A recipient whose checkout has no relationship to the sender's repo at all — the freshness check will always report the histories as unrelated, and the handoff degrades to "read this text," which a normal message would do just as well.