Skip to content

feat(create): accept a native config, making snapshots a clone source - #20

Merged
chuckmeyer merged 3 commits into
mainfrom
feat/native-create
Aug 27, 2026
Merged

chuckmeyer merged 3 commits into
mainfrom
feat/native-create

Conversation

@chuckmeyer

Copy link
Copy Markdown
Contributor

Phase 3 of #14 — completes the plan.

What this adds

create now detects a native config the same way update does, so a snapshot is a complete clone source, not just a way to manage the agent it came from:

algolia-agent snapshot <source_id> -o clone/agent-config.json
# edit the name
algolia-agent create --config clone/agent-config.json

The copy carries everything the friendly format cannot express: extra tools, mode, allowUnlistedIndices, per-index searchControls, and the full config block.

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. The source in the live test was published; the clone came out 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 a clear error pointing at algolia-agent providers rather than an opaque API rejection.

No guard is needed here — create has no prior state to lose, which is why this was separable from phases 1 and 2.

Reuse

The native path shares _native_payload() with update, so the literal-prompt rule and the rejection of --index/--replica/--provider/--var come along rather than being reimplemented. Its --var message now says "run the command again" instead of naming update.

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:

fields differing from the source, excluding identity none
status published → draft (forced)
carried both tools, mode: dynamic, allowUnlistedIndices: true, searchControls, all 7 config keys, templateType: shopping-assistant, claude-fable-5, literal {{INSERT_BRAND}}
clone's own snapshot round-trip clean

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, and snapshot refuses 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 snapshot available, the destructive path is only reachable from a hand-written partial config plus --force, which is a deliberate act.

🤖 Generated with Claude Code

chuckmeyer and others added 2 commits August 26, 2026 20:53
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>
@chuckmeyer

Copy link
Copy Markdown
Contributor Author

Added: a templated prompt is never overwritten, even with --force

Raised in review that a converged prompt must not be able to overwrite a templatized one. It was already protected at two levels — snapshot refuses outright when the file exists, and with --force it warned that the template could not be recovered — but the --force case was a warning that proceeded.

Given the loss is genuinely unrecoverable (rendered text cannot be un-rendered, and the template never existed server-side), that is now a refusal:

$ algolia-agent snapshot <id> --force
ERROR: PROMPT.md contains template variables ({{booth}}, {{event_name}}) and cannot be
recovered from rendered server state.

Write the prompt elsewhere with:
  --instructions-file PROMPT.snapshot.md
or delete PROMPT.md first if you meant to replace it.

The tradeoff, stated plainly: --force is no longer absolute. It still overwrites plain prompt files — a snapshot can reproduce those — but a templated one requires either routing the output elsewhere or deleting the file, which makes the intent explicit. Deliberate, since the alternative is a warning that scrolls past and takes the template with it.

Verified against this repo's own examples/tcg/PROMPT.md: refused with --force, {{booth}} and {{event_name}} intact afterwards, and --instructions-file PROMPT.snapshot.md writes the rendered prompt alongside the template as suggested.

160 tests (3 new): the refusal, --force still overwriting a plain prompt, and the suggested remedy actually working end to end.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 create and implement _create_from_native() (forces status: draft, requires providerId, reuses _native_payload()).
  • Change snapshot to refuse overwriting templated .md prompts 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.

Comment thread src/algolia_agent/cli.py
Comment on lines +628 to 635
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."
)
Comment thread src/algolia_agent/cli.py Outdated
Comment on lines +729 to +735
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>
@chuckmeyer

Copy link
Copy Markdown
Contributor Author

Both findings applied.

Templated refusal suggested the wrong flag for SYSTEM.md — correct. The check now walks the known prompt targets paired with their own flag (instr_path → --instructions-file, system_path → --system-prompt-file) instead of filtering on a .md suffix.

That restructuring fixed a second bug I had not spotted: keying on the suffix meant a templated prompt under a non-.md name slipped through entirely, and --instructions-file PROMPT.txt is perfectly legal. Verified live — it is now caught, with the right extension in the suggestion:

$ algolia-agent snapshot <id> --instructions-file PROMPT.txt --force
ERROR: .../PROMPT.txt contains template variables ({{event_name}}) and cannot be
recovered from rendered server state.

Write it elsewhere with:
  --instructions-file PROMPT.snapshot.txt

Missing-fields error always cited providerId — also correct, and misleading for the two fields that have CLI overrides. It now names the file to edit, mentions --name/--model when those are what is missing, and only raises providers for providerId:

$ algolia-agent create --config native.json
ERROR: native config is missing required fields: name
Add them to .../native.json, or pass --name.

164 tests (4 new): the SYSTEM.md flag, the non-.md prompt name, and both branches of the missing-fields message.

@chuckmeyer
chuckmeyer merged commit a3269d1 into main Aug 27, 2026
3 checks passed
@chuckmeyer
chuckmeyer deleted the feat/native-create branch August 27, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants