feat(create): accept a native config, making snapshots a clone source - #20
Conversation
Phase 3 of #14, completing the plan. `create` now detects a native config the same way `update` does and sends it as the file describes, so a snapshot is a complete clone source rather than only a way to manage the agent it came from. No guard is needed: create has no prior state to lose. Two deliberate divergences from the file: - status is forced to draft. Creating from a snapshot of a live agent should not silently publish the copy, and create is documented as producing a draft. - providerId is required. A native config carries it directly and there is no provider-name resolution on this path, so a missing one is an error pointing at `algolia-agent providers` rather than a confusing API rejection. Reuses _native_payload, so the literal-prompt rule and the rejection of --index/--replica/--provider/--var are shared with update rather than reimplemented. Its --var message now says "run the command again" instead of naming update. The friendly path is untouched: it still resolves a provider name and builds one tool, covered by a regression test. Verified live by cloning the template-created shopping-assistant agent. The copy differed from its source in nothing but identity and the forced draft status — tools, mode, allowUnlistedIndices, searchControls, all seven config keys, templateType, claude-fable-5, and a literal {{INSERT_BRAND}} all carried — and the clone's own snapshot round-tripped clean. Clone deleted afterwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A snapshot holds rendered server state, so replacing a templated prompt swaps
{{placeholders}} for the values they resolved to. That loss is unrecoverable —
the template only ever existed locally and rendered text cannot be un-rendered —
so it is outside what --force should cover. Previously this was a stderr warning
that proceeded anyway.
The refusal names the variables it found and suggests
--instructions-file <name>.snapshot.md, which writes the rendered prompt beside
the template rather than over it. Deleting the file remains the way to say you
meant it.
Plain prompt files are still overwritten by --force, because a snapshot can
reproduce them.
Verified against the repo's own templated TCG prompt: refused with --force,
{{booth}} and {{event_name}} intact, and the suggested remedy writes alongside.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Added: a templated prompt is never overwritten, even with
|
There was a problem hiding this comment.
Pull request overview
Enables algolia-agent create to accept native (snapshot) configs the same way update does, allowing snapshots to be used as a full-fidelity clone source. It also tightens snapshot overwrite behavior around templated prompts and documents the new cloning workflow.
Changes:
- Add native-config detection to
createand implement_create_from_native()(forcesstatus: draft, requiresproviderId, reuses_native_payload()). - Change
snapshotto refuse overwriting templated.mdprompts even with--force, with an actionable remediation message. - Add/expand regression tests and README documentation for the new behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/algolia_agent/cli.py |
Adds native-create path, refactors create output reporting, and changes snapshot overwrite handling for templated prompts. |
tests/test_cli.py |
Adds tests for snapshot templated-prompt refusal/overwrite behavior and for native-config create behavior (payload fidelity, forced draft, required providerId, dry-run). |
README.md |
Documents new snapshot overwrite rule for templated prompts and the “snapshot → create” cloning workflow. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| listed = ", ".join("{{" + v + "}}" for v in sorted(template_vars)) | ||
| raise SystemExit( | ||
| f"ERROR: {p} contains template variables ({listed}) and cannot be\n" | ||
| "recovered from rendered server state.\n\n" | ||
| "Write the prompt elsewhere with:\n" | ||
| f" --instructions-file {p.stem}.snapshot{p.suffix}\n" | ||
| f"or delete {p.name} first if you meant to replace it." | ||
| ) |
| missing = [k for k in ("name", "providerId", "model") if not payload.get(k)] | ||
| if missing: | ||
| raise SystemExit( | ||
| f"ERROR: native config is missing required fields: {', '.join(missing)}\n" | ||
| "A native config carries providerId directly rather than a provider name; " | ||
| "run `algolia-agent providers` to look one up." | ||
| ) |
Two review findings on this PR. The templated-prompt refusal always suggested --instructions-file, but the templated file can be the system prompt, where --system-prompt-file is the remedy. The check now walks the known prompt targets paired with their flags instead of filtering on a .md suffix — which also catches a templated prompt under a non-.md name, since --instructions-file PROMPT.txt is legal and was slipping past. The native-create missing-fields error always cited providerId and pointed at `algolia-agent providers`, even when the missing field was name or model. It now names the config file to edit, mentions --name/--model when those are what is missing, and only raises providers for providerId. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both findings applied. Templated refusal suggested the wrong flag for That restructuring fixed a second bug I had not spotted: keying on the suffix meant a templated prompt under a non- Missing-fields error always cited 164 tests (4 new): the |
Phase 3 of #14 — completes the plan.
What this adds
createnow detects a native config the same wayupdatedoes, so a snapshot is a complete clone source, not just a way to manage the agent it came from:The copy carries everything the friendly format cannot express: extra tools,
mode,allowUnlistedIndices, per-indexsearchControls, and the fullconfigblock.Two deliberate divergences from the file
statusis forced todraft. Creating from a snapshot of a live agent should not silently publish the copy, andcreateis documented as producing a draft. The source in the live test waspublished; the clone came outdraft.providerIdis required. A native config carries it directly and there is no provider-name resolution on this path, so a missing one is a clear error pointing atalgolia-agent providersrather than an opaque API rejection.No guard is needed here —
createhas no prior state to lose, which is why this was separable from phases 1 and 2.Reuse
The native path shares
_native_payload()withupdate, so the literal-prompt rule and the rejection of--index/--replica/--provider/--varcome along rather than being reimplemented. Its--varmessage now says "run the command again" instead of namingupdate.The friendly path is untouched, with a regression test asserting it still resolves a provider name and builds exactly one tool.
Verification
158 tests pass (5 new). Live, by cloning the template-created shopping-assistant agent:
statuspublished→draft(forced)mode: dynamic,allowUnlistedIndices: true,searchControls, all 7configkeys,templateType: shopping-assistant,claude-fable-5, literal{{INSERT_BRAND}}Clone deleted and confirmed gone.
One thing the live test surfaced, working as designed: snapshotting two agents into the same directory collides on the default
PROMPT.md, andsnapshotrefuses rather than overwriting. My first attempt at the clone test hit exactly that and I had suppressed the output, so the failure looked like a passing round-trip until I re-ran it visibly. Documented in the README: snapshot each agent into its own directory.Status of #14
Phases 1–3 complete. Phase 4 needs no code: with
snapshotavailable, the destructive path is only reachable from a hand-written partial config plus--force, which is a deliberate act.🤖 Generated with Claude Code