Follows on from issue #3, "Markitdown integration for external-document evidence" — that issue's markitdown integration shipped without LLM image captioning; this file tracks that specific follow-up separately, as agreed when #3 was implemented.
Status
Not implemented, intentionally. .agents/skills/agent-ready-context/scripts/prepare_external_evidence.py
accepts image sources (.jpg/.jpeg/.png) today, but they carry limited value: only
EXIF/IPTC metadata is extracted (via the optional exiftool binary), with no visual
content extraction. This is a deliberate no-op, not a bug — this issue tracks turning it
into a real capability later, with full user disclosure, once the project has a
consistent story for provider credentials (see Resources). No hints or scaffolding for
this are wired into the skill yet; that is deliberate too, until this is actually adopted.
Why this isn't a regression
Verified before filing, not assumed: the OpenKB-era pipeline (predecessor to the current
OpenWiki-based pipeline) never had PDF/Office/image ingestion of any kind, and never had
LLM-based image analysis. Confirmed against:
git show c275aca:.agents/skills/agent-ready-context/references/external-docs.md — the
OpenKB-era doc covers URL-text evidence only (web tool fetch, or openkb add <url>); no
mention of PDF, Office, images, OCR, or vision anywhere.
- The OpenKB migration project memory (sessions from 2026-07-07 through 2026-07-10) — an
extensive, session-by-session record of OpenKB's actual capabilities; no image/vision/OCR
feature appears anywhere in it.
So this is a net-new capability request, not a gap the OpenWiki migration reopened.
The two regressions that migration did reopen (no PDF/Office ingestion, no URL ingestion)
are already closed by the markitdown integration that shipped alongside this issue.
What's already verified (do not re-derive)
Checked directly against the installed markitdown 0.1.6 package source, not just its
docs:
- The CLI (
markitdown <file>) has no flag for LLM image captioning. Confirmed by
markitdown --help and by grepping __main__.py for llm_client/llm_model/
llm_prompt — zero matches. The feature exists only on the Python MarkItDown class
constructor: _markitdown.py
(__init__/enable_builtins, around the llm_client/llm_model/llm_prompt kwargs).
- The actual call site is
converters/_llm_caption.py:
client.chat.completions.create(model=model, messages=[...]) — the standard OpenAI SDK
shape, image passed as a base64 data URI. Any object duck-typed to that interface works,
including openai.OpenAI(base_url=..., api_key=...) pointed at a local OpenAI-compatible
engine (Ollama/vLLM/llama.cpp) — the same shape OpenWiki already uses for its air-gapped
mode, so this can mirror that pattern structurally.
- Non-LLM image handling (what runs today) is in
converters/_image_converter.py:
EXIF/IPTC metadata only (ImageSize, Title, Caption, Description, Keywords,
Artist, Author, DateTimeOriginal, CreateDate, GPSPosition), via the optional
exiftool binary (auto-detected via the EXIFTOOL_PATH env var or PATH). No OCR
anywhere in the package — verified with a repo-wide grep for ocr/tesseract; the only
hit is inside the unwired Azure Document Intelligence converter, which does OCR remotely
in Azure, never locally. Those metadata fields are usually empty for the
screenshots/diagrams this pipeline actually processes, so today's image support is
low-value by design, not by bug.
- Related, already shipped, for contrast: YouTube URL support (added alongside this issue)
needs no LLM at all —
converters/_youtube_converter.py
uses youtube_transcript_api.YouTubeTranscriptApi plus a plain page fetch (bs4), no
openai/llm_client anywhere in that file. It just retrieves YouTube's own pre-existing
caption track over the network — same trust tier as fetching any webpage, no credentials
needed. Do not conflate the two when reasoning about this issue.
Proposed shape (decided in principle, not built)
- New, separate optional script (not a flag on
prepare_external_evidence.py) — keep
that script dependency-free and CLI-only (its deliberate existing design); the new script
would need real third-party deps (markitdown, openai) via PEP 723
dependencies = [...], which has precedent in this skill (run_openwiki_staged.py
already declares pyyaml).
- Credentials: standard OpenAI SDK env vars (
OPENAI_API_KEY / OPENAI_BASE_URL),
read internally by the real openai SDK (openai.OpenAI() with no explicit args) — this
repo's own code never reads or touches the key value, matching the hands-off guarantee
already used for OpenWiki credentials. Model name via an explicit --llm-model flag,
never silently defaulted.
- Config loading: the user wants this sourced from project/user-scoped config rather
than requiring the caller to export shell vars on every invocation — but wants it
designed once, consistently, alongside the config mechanism planned for the langmem
work in discussions#4, not as a
markitdown-specific one-off. Resolve that shared mechanism first, then wire this script
to it, rather than inventing a second config convention.
- Disclosure: before every call, state tool pin (markitdown + openai SDK versions),
provider/model (from config, name only), endpoint family (host, not full URL/creds),
credential source (env var / config key name only, never the value), content sent (the
image file), and cost boundary — mirroring
references/privacy-and-data-flows.md's existing OpenWiki disclosure rule. Never invoke
without fresh consent; never silently pick a provider.
- Draft/review contract stays identical to the rest of this pipeline: still writes to
okf/.okf-build/external/, never okf/external/ directly; same review checklist
(prompt injection, PII, size) before promotion.
Open questions for the adopting session
- The exact shared config mechanism (once the langmem discussion lands) — file format,
precedence order (project vs. user vs. shell env), and whether it needs a new tiny
loader or can reuse an existing library (e.g. python-dotenv).
- Exact new script name and CLI shape, e.g.
caption_image_with_llm.py --repo . --source <image> --llm-model <model> --resource <canonical-uri>.
- Whether to cap image size/dimensions before base64-encoding (cost and payload-size
guard) — _llm_caption.py sends the raw file bytes as-is today.
- Whether images markitdown itself doesn't accept for this converter
(ACCEPTED_FILE_EXTENSIONS = [".jpg", ".jpeg", ".png"] in _image_converter.py; no
.gif/.webp/.bmp) should be pre-converted, or stay out of scope.
Files likely touched at adoption time
- NEW
.agents/skills/agent-ready-context/scripts/<name>.py
.agents/skills/agent-ready-context/references/external-docs.md — new section
.agents/skills/agent-ready-context/references/dependencies.md — new pin row(s) for
openai (and a version bump if markitdown's own pin needs to move)
.agents/skills/agent-ready-context/references/privacy-and-data-flows.md — new
data-flow row + disclosure text
.agents/skills/agent-ready-context/SKILL.md — workflow step + command block
.agents/skills/agent-ready-context/scripts/check_prereqs.py — optional tool check
tests/test_openwiki_adapter.py — new test class, CLI/LLM calls mocked, never required
in CI
AGENTS.md — new pin row + disclosure note, at adoption time only; no version literals
belong in the skill files themselves
Resources
Acceptance criteria (draft, refine at adoption time)
- LLM image captioning is strictly optional; every existing flow and gate passes with it
absent, same as markitdown itself is optional today.
- No credential value is ever read, printed, or logged by this repo's own code — the
openai SDK (or equivalent) reads it directly from the resolved config/env.
- Full disclosure (tool pin, provider/model, endpoint, credential source, content sent,
cost boundary) before every call; consent is per-call, not standing.
- Tests pass without the real
markitdown/openai packages installed (mocked), never
required in CI.
- No version literals in skill files themselves — pins live only in the consuming
repository's AGENTS.md, same policy as every other tool here.
Follows on from issue #3, "Markitdown integration for external-document evidence" — that issue's markitdown integration shipped without LLM image captioning; this file tracks that specific follow-up separately, as agreed when #3 was implemented.
Status
Not implemented, intentionally.
.agents/skills/agent-ready-context/scripts/prepare_external_evidence.pyaccepts image sources (
.jpg/.jpeg/.png) today, but they carry limited value: onlyEXIF/IPTC metadata is extracted (via the optional
exiftoolbinary), with no visualcontent extraction. This is a deliberate no-op, not a bug — this issue tracks turning it
into a real capability later, with full user disclosure, once the project has a
consistent story for provider credentials (see Resources). No hints or scaffolding for
this are wired into the skill yet; that is deliberate too, until this is actually adopted.
Why this isn't a regression
Verified before filing, not assumed: the OpenKB-era pipeline (predecessor to the current
OpenWiki-based pipeline) never had PDF/Office/image ingestion of any kind, and never had
LLM-based image analysis. Confirmed against:
git show c275aca:.agents/skills/agent-ready-context/references/external-docs.md— theOpenKB-era doc covers URL-text evidence only (web tool fetch, or
openkb add <url>); nomention of PDF, Office, images, OCR, or vision anywhere.
extensive, session-by-session record of OpenKB's actual capabilities; no image/vision/OCR
feature appears anywhere in it.
So this is a net-new capability request, not a gap the OpenWiki migration reopened.
The two regressions that migration did reopen (no PDF/Office ingestion, no URL ingestion)
are already closed by the markitdown integration that shipped alongside this issue.
What's already verified (do not re-derive)
Checked directly against the installed
markitdown0.1.6 package source, not just itsdocs:
markitdown <file>) has no flag for LLM image captioning. Confirmed bymarkitdown --helpand by grepping__main__.pyforllm_client/llm_model/llm_prompt— zero matches. The feature exists only on the PythonMarkItDownclassconstructor:
_markitdown.py(
__init__/enable_builtins, around thellm_client/llm_model/llm_promptkwargs).converters/_llm_caption.py:client.chat.completions.create(model=model, messages=[...])— the standard OpenAI SDKshape, image passed as a base64 data URI. Any object duck-typed to that interface works,
including
openai.OpenAI(base_url=..., api_key=...)pointed at a local OpenAI-compatibleengine (Ollama/vLLM/llama.cpp) — the same shape OpenWiki already uses for its air-gapped
mode, so this can mirror that pattern structurally.
converters/_image_converter.py:EXIF/IPTC metadata only (
ImageSize,Title,Caption,Description,Keywords,Artist,Author,DateTimeOriginal,CreateDate,GPSPosition), via the optionalexiftoolbinary (auto-detected via theEXIFTOOL_PATHenv var orPATH). No OCRanywhere in the package — verified with a repo-wide grep for
ocr/tesseract; the onlyhit is inside the unwired Azure Document Intelligence converter, which does OCR remotely
in Azure, never locally. Those metadata fields are usually empty for the
screenshots/diagrams this pipeline actually processes, so today's image support is
low-value by design, not by bug.
needs no LLM at all —
converters/_youtube_converter.pyuses
youtube_transcript_api.YouTubeTranscriptApiplus a plain page fetch (bs4), noopenai/llm_clientanywhere in that file. It just retrieves YouTube's own pre-existingcaption track over the network — same trust tier as fetching any webpage, no credentials
needed. Do not conflate the two when reasoning about this issue.
Proposed shape (decided in principle, not built)
prepare_external_evidence.py) — keepthat script dependency-free and CLI-only (its deliberate existing design); the new script
would need real third-party deps (
markitdown,openai) via PEP 723dependencies = [...], which has precedent in this skill (run_openwiki_staged.pyalready declares
pyyaml).OPENAI_API_KEY/OPENAI_BASE_URL),read internally by the real
openaiSDK (openai.OpenAI()with no explicit args) — thisrepo's own code never reads or touches the key value, matching the hands-off guarantee
already used for OpenWiki credentials. Model name via an explicit
--llm-modelflag,never silently defaulted.
than requiring the caller to export shell vars on every invocation — but wants it
designed once, consistently, alongside the config mechanism planned for the langmem
work in discussions#4, not as a
markitdown-specific one-off. Resolve that shared mechanism first, then wire this script
to it, rather than inventing a second config convention.
provider/model (from config, name only), endpoint family (host, not full URL/creds),
credential source (env var / config key name only, never the value), content sent (the
image file), and cost boundary — mirroring
references/privacy-and-data-flows.md's existing OpenWiki disclosure rule. Never invokewithout fresh consent; never silently pick a provider.
okf/.okf-build/external/, neverokf/external/directly; same review checklist(prompt injection, PII, size) before promotion.
Open questions for the adopting session
precedence order (project vs. user vs. shell env), and whether it needs a new tiny
loader or can reuse an existing library (e.g.
python-dotenv).caption_image_with_llm.py --repo . --source <image> --llm-model <model> --resource <canonical-uri>.guard) —
_llm_caption.pysends the raw file bytes as-is today.(
ACCEPTED_FILE_EXTENSIONS = [".jpg", ".jpeg", ".png"]in_image_converter.py; no.gif/.webp/.bmp) should be pre-converted, or stay out of scope.Files likely touched at adoption time
.agents/skills/agent-ready-context/scripts/<name>.py.agents/skills/agent-ready-context/references/external-docs.md— new section.agents/skills/agent-ready-context/references/dependencies.md— new pin row(s) foropenai(and a version bump if markitdown's own pin needs to move).agents/skills/agent-ready-context/references/privacy-and-data-flows.md— newdata-flow row + disclosure text
.agents/skills/agent-ready-context/SKILL.md— workflow step + command block.agents/skills/agent-ready-context/scripts/check_prereqs.py— optional tool checktests/test_openwiki_adapter.py— new test class, CLI/LLM calls mocked, never requiredin CI
AGENTS.md— new pin row + disclosure note, at adoption time only; no version literalsbelong in the skill files themselves
Resources
currently
0.1.6per this repo'sAGENTS.md)(for contrast: no LLM used, network-only via
youtube_transcript_api)_llm_caption.pyexpects):https://github.com/openai/openai-python
Proposal: add harness-captured, LangMem-assisted repository dreaming to the `agent-ready-context` lifecycle #4
.agents/skills/agent-ready-context/references/privacy-and-data-flows.md,.agents/skills/agent-ready-context/references/openwiki-providers.mdAcceptance criteria (draft, refine at adoption time)
absent, same as markitdown itself is optional today.
openaiSDK (or equivalent) reads it directly from the resolved config/env.cost boundary) before every call; consent is per-call, not standing.
markitdown/openaipackages installed (mocked), neverrequired in CI.
repository's
AGENTS.md, same policy as every other tool here.