RunRequest: add system_mode (append|replace)
Why this matters:
- Default 'append' (--append-system-prompt): caller's system text
is ADDED to Claude's default base prompt ('I am Claude, an AI
assistant...'). Right for tool-using assistants and coding agents
where the helpful-honest defaults are useful substrate. Every
existing caller (cauldron, petalparse, etc) ran in this mode
implicitly.
- New 'replace' (--system-prompt): caller's system text REPLACES
the default base prompt entirely. Right for personas — fiction
authors, chat bots, in-world characters — where Claude's defaults
would bleed through as friction. The model BECOMES the persona
rather than 'Claude playing the persona.'
Trigger for adding this: skald v0.3 — authors as personas with
souls. Orson Black writing a Chernobyl piece can't have Claude's
default helpfulness softening the prose; the soul needs to be the
prompt, not append to it.
Wire shape:
- server.py: Literal['append','replace']|None in RunRequest, default 'append'
- runner.py: branch on system_mode to pick the right CLI flag
- clients/rust: SystemMode enum, Option<SystemMode> on RunRequest
Backward compatible: clients that don't set system_mode get append
semantics (the existing behavior).
This commit is contained in:
parent
015348c526
commit
d4c3a9d2de
4 changed files with 66 additions and 4 deletions
|
|
@ -33,6 +33,7 @@ class Runner:
|
|||
prompt: str,
|
||||
model: str | None = None,
|
||||
system: str | None = None,
|
||||
system_mode: str | None = "append",
|
||||
files: list[str] | None = None,
|
||||
timeout_secs: int | None = None,
|
||||
) -> RunResult:
|
||||
|
|
@ -50,7 +51,15 @@ class Runner:
|
|||
"--model", model or self.default_model,
|
||||
]
|
||||
if system:
|
||||
cmd += ["--append-system-prompt", system]
|
||||
# "append" = additive on top of claude's defaults; right
|
||||
# for tool-using assistants. "replace" = REPLACES the
|
||||
# default base prompt entirely; right for personas
|
||||
# (fiction authors, chat bots, in-world characters) where
|
||||
# claude's defaults would bleed through as friction.
|
||||
if system_mode == "replace":
|
||||
cmd += ["--system-prompt", system]
|
||||
else:
|
||||
cmd += ["--append-system-prompt", system]
|
||||
if files:
|
||||
for f in files:
|
||||
cmd += ["--files", f]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import os
|
|||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Literal
|
||||
|
||||
from fastapi import FastAPI, Header, HTTPException, Request, UploadFile, File, Form
|
||||
from fastapi.responses import JSONResponse
|
||||
|
|
@ -103,6 +103,25 @@ class RunRequest(BaseModel):
|
|||
prompt: str = Field(min_length=1)
|
||||
model: str | None = None
|
||||
system: str | None = None
|
||||
# How `system` gets passed to the underlying claude CLI:
|
||||
#
|
||||
# "append" → `--append-system-prompt`. Claude's default base
|
||||
# prompt stays + the caller's `system` is added.
|
||||
# Right for assistants / tool-use / coding agents
|
||||
# where Claude's helpful-honest defaults are
|
||||
# desirable substrate.
|
||||
#
|
||||
# "replace" → `--system-prompt`. Claude's default base prompt
|
||||
# is REPLACED entirely by the caller's `system`.
|
||||
# Right for personas (fiction authors, chat bots,
|
||||
# in-world characters) where Claude's defaults
|
||||
# would bleed through as friction. The model
|
||||
# BECOMES the persona instead of "Claude playing
|
||||
# the persona."
|
||||
#
|
||||
# Default is "append" for back-compat — every caller before this
|
||||
# field landed used append semantics implicitly.
|
||||
system_mode: Literal["append", "replace"] | None = "append"
|
||||
files: list[str] | None = None
|
||||
timeout_secs: int | None = Field(default=None, ge=5, le=600)
|
||||
|
||||
|
|
@ -183,6 +202,7 @@ def run(
|
|||
prompt=body.prompt,
|
||||
model=body.model,
|
||||
system=body.system,
|
||||
system_mode=body.system_mode,
|
||||
files=file_paths or None,
|
||||
timeout_secs=body.timeout_secs,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,5 +54,5 @@ pub use error::Error;
|
|||
pub use session::{Session, SessionList, SessionOptions, SessionState, TurnEvent, TurnResult};
|
||||
pub use types::{
|
||||
AppToken, AppTokenInfo, FileToken, Healthz, RunFailure, RunRequest, RunResult,
|
||||
TokenCreateRequest, TokenList,
|
||||
SystemMode, TokenCreateRequest, TokenList,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,23 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|||
|
||||
use crate::error::Error;
|
||||
|
||||
/// How `system` is passed to `claude -p`. See [`RunRequest::system_mode`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SystemMode {
|
||||
/// `--append-system-prompt` — additive on top of Claude's defaults.
|
||||
Append,
|
||||
/// `--system-prompt` — REPLACES Claude's default base prompt
|
||||
/// entirely. Right for personas.
|
||||
Replace,
|
||||
}
|
||||
|
||||
impl Default for SystemMode {
|
||||
fn default() -> Self {
|
||||
Self::Append
|
||||
}
|
||||
}
|
||||
|
||||
/// `GET /healthz` response body.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Healthz {
|
||||
|
|
@ -43,10 +60,26 @@ pub struct RunRequest {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// System prompt appended via `claude -p --append-system-prompt`.
|
||||
/// System prompt. How it's passed to `claude -p` depends on
|
||||
/// [`Self::system_mode`].
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system: Option<String>,
|
||||
|
||||
/// How `system` is passed to `claude -p`:
|
||||
///
|
||||
/// - [`SystemMode::Append`] (default) → `--append-system-prompt`.
|
||||
/// Claude's default base prompt stays + `system` is added.
|
||||
/// Right for tool-using assistants where Claude's helpful-
|
||||
/// honest defaults are useful substrate.
|
||||
/// - [`SystemMode::Replace`] → `--system-prompt`. Replaces the
|
||||
/// base prompt entirely. Right for personas (fiction authors,
|
||||
/// chat bots, in-world characters) where Claude's defaults
|
||||
/// would bleed through as friction.
|
||||
///
|
||||
/// `None` = server default (currently `append`).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system_mode: Option<SystemMode>,
|
||||
|
||||
/// File tokens previously returned from [`Client::upload_file`].
|
||||
///
|
||||
/// [`Client::upload_file`]: crate::Client::upload_file
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue