- task schema: optional session (named id) -> persistent session dir at dataRoot/sessions/<name>, isolated per name - pi adapter: --session-dir when declared (ephemeral --no-session stays the default otherwise); -c resumes the most recent session when present - compose passthrough; result.json records session - fixtures: tasks/session-demo-1.json (teach) + session-demo-2.json (recall) - E2E: teach -> REMEMBERED + host-side session JSONL; resume -> recalled 'mosaico' exactly; single continued session file Closes #22, closes #23
This commit is contained in:
+13
-1
@@ -18,6 +18,18 @@ if [ -n "${MOSAIC_WORKSPACE:-}" ]; then
|
|||||||
cd "$MOSAIC_WORKSPACE"
|
cd "$MOSAIC_WORKSPACE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Session (M6): persistent named session directory; resume the most recent
|
||||||
|
# session in that directory when one exists (pi documented flags).
|
||||||
|
# Default remains ephemeral (--no-session) when no session is declared.
|
||||||
|
SESSION_FLAGS="--no-session"
|
||||||
|
if [ -n "${MOSAIC_SESSION_DIR:-}" ]; then
|
||||||
|
mkdir -p "$MOSAIC_SESSION_DIR"
|
||||||
|
SESSION_FLAGS="--session-dir $MOSAIC_SESSION_DIR"
|
||||||
|
if [ -n "$(ls -A "$MOSAIC_SESSION_DIR" 2>/dev/null)" ]; then
|
||||||
|
SESSION_FLAGS="$SESSION_FLAGS -c"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Capabilities (M5): explicit allowlist or no tools.
|
# Capabilities (M5): explicit allowlist or no tools.
|
||||||
TOOLS_FLAG="--no-tools"
|
TOOLS_FLAG="--no-tools"
|
||||||
[ -n "${MOSAIC_TOOLS:-}" ] && TOOLS_FLAG="--tools $MOSAIC_TOOLS"
|
[ -n "${MOSAIC_TOOLS:-}" ] && TOOLS_FLAG="--tools $MOSAIC_TOOLS"
|
||||||
@@ -30,13 +42,13 @@ TOOLS_FLAG="--no-tools"
|
|||||||
# --offline no startup network operations (update checks/telemetry)
|
# --offline no startup network operations (update checks/telemetry)
|
||||||
exec pi \
|
exec pi \
|
||||||
--offline \
|
--offline \
|
||||||
--no-session \
|
|
||||||
--no-extensions \
|
--no-extensions \
|
||||||
--no-skills \
|
--no-skills \
|
||||||
--no-prompt-templates \
|
--no-prompt-templates \
|
||||||
--no-themes \
|
--no-themes \
|
||||||
--no-context-files \
|
--no-context-files \
|
||||||
$TOOLS_FLAG \
|
$TOOLS_FLAG \
|
||||||
|
$SESSION_FLAGS \
|
||||||
--provider "$PI_PROVIDER" \
|
--provider "$PI_PROVIDER" \
|
||||||
--model "$PI_MODEL" \
|
--model "$PI_MODEL" \
|
||||||
--system-prompt "$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")" \
|
--system-prompt "$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")" \
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ services:
|
|||||||
# Workspace + capabilities (set by the task runner; M5)
|
# Workspace + capabilities (set by the task runner; M5)
|
||||||
MOSAIC_WORKSPACE: ${MOSAIC_WORKSPACE:-}
|
MOSAIC_WORKSPACE: ${MOSAIC_WORKSPACE:-}
|
||||||
MOSAIC_TOOLS: ${MOSAIC_TOOLS:-}
|
MOSAIC_TOOLS: ${MOSAIC_TOOLS:-}
|
||||||
|
# Persistent named session dir (set by the task runner; M6)
|
||||||
|
MOSAIC_SESSION_DIR: ${MOSAIC_SESSION_DIR:-}
|
||||||
# mock adapter only: verbatim response for deterministic seam tests
|
# mock adapter only: verbatim response for deterministic seam tests
|
||||||
MOSAIC_MOCK_RESPONSE: ${MOSAIC_MOCK_RESPONSE:-}
|
MOSAIC_MOCK_RESPONSE: ${MOSAIC_MOCK_RESPONSE:-}
|
||||||
# Documented container auth alternative: provider API key via
|
# Documented container auth alternative: provider API key via
|
||||||
|
|||||||
+20
-1
@@ -106,7 +106,7 @@ function validateMission(document, file) {
|
|||||||
|
|
||||||
function validateTask(document, file) {
|
function validateTask(document, file) {
|
||||||
if (!isPlainObject(document)) fail(2, "task must be a JSON object");
|
if (!isPlainObject(document)) fail(2, "task must be a JSON object");
|
||||||
rejectUnknownKeys(document, ["taskVersion", "id", "prompt", "mission", "expectExact", "timeoutSeconds", "workspace", "capabilities"], "task");
|
rejectUnknownKeys(document, ["taskVersion", "id", "prompt", "mission", "expectExact", "timeoutSeconds", "workspace", "capabilities", "session"], "task");
|
||||||
if (document.taskVersion !== 1) {
|
if (document.taskVersion !== 1) {
|
||||||
fail(2, `unsupported taskVersion: ${JSON.stringify(document.taskVersion)} (supported: 1)`);
|
fail(2, `unsupported taskVersion: ${JSON.stringify(document.taskVersion)} (supported: 1)`);
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,17 @@ function validateTask(document, file) {
|
|||||||
workspace = document.workspace;
|
workspace = document.workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Session (M6): optional named persistent session under
|
||||||
|
// <dataRoot>/sessions/<name>. Distinct names never share state.
|
||||||
|
let session = null;
|
||||||
|
if (document.session !== undefined && document.session !== null) {
|
||||||
|
if (typeof document.session !== "string" || document.session.length === 0) {
|
||||||
|
fail(2, 'task "session" must be a non-empty string when present');
|
||||||
|
}
|
||||||
|
validateId(document.session, "task session");
|
||||||
|
session = document.session;
|
||||||
|
}
|
||||||
|
|
||||||
// Capabilities (M5): optional tools allowlist mapped by adapters to their
|
// Capabilities (M5): optional tools allowlist mapped by adapters to their
|
||||||
// native permission flags. Absent = no tools.
|
// native permission flags. Absent = no tools.
|
||||||
let tools = null;
|
let tools = null;
|
||||||
@@ -189,6 +200,7 @@ function validateTask(document, file) {
|
|||||||
timeoutSeconds,
|
timeoutSeconds,
|
||||||
workspace,
|
workspace,
|
||||||
tools,
|
tools,
|
||||||
|
session,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +283,12 @@ function runTask(taskFile) {
|
|||||||
if (workspaceContainerPath) spawnEnv.MOSAIC_WORKSPACE = workspaceContainerPath;
|
if (workspaceContainerPath) spawnEnv.MOSAIC_WORKSPACE = workspaceContainerPath;
|
||||||
spawnEnv.MOSAIC_TOOLS = task.tools ? task.tools.join(",") : "";
|
spawnEnv.MOSAIC_TOOLS = task.tools ? task.tools.join(",") : "";
|
||||||
|
|
||||||
|
// Session (M6): persistent named session dir, passed as container path.
|
||||||
|
if (task.session) {
|
||||||
|
fs.mkdirSync(path.join(resolved.dataRoot, "sessions", task.session), { recursive: true });
|
||||||
|
spawnEnv.MOSAIC_SESSION_DIR = `/var/lib/mosaic/sessions/${task.session}`;
|
||||||
|
}
|
||||||
|
|
||||||
const proc = spawnSync(
|
const proc = spawnSync(
|
||||||
"docker",
|
"docker",
|
||||||
["compose", "run", "--rm", "-T", "mosaic-agent", task.prompt],
|
["compose", "run", "--rm", "-T", "mosaic-agent", task.prompt],
|
||||||
@@ -319,6 +337,7 @@ function runTask(taskFile) {
|
|||||||
expectedExact: expected,
|
expectedExact: expected,
|
||||||
workspace: task.workspace,
|
workspace: task.workspace,
|
||||||
tools: task.tools,
|
tools: task.tools,
|
||||||
|
session: task.session,
|
||||||
exitCode: proc.status,
|
exitCode: proc.status,
|
||||||
signal: proc.signal ?? null,
|
signal: proc.signal ?? null,
|
||||||
provider: resolved.execution.provider,
|
provider: resolved.execution.provider,
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"taskVersion": 1,
|
||||||
|
"id": "t-session-teach",
|
||||||
|
"prompt": "Remember this code word for later: mosaico. Reply with exactly: REMEMBERED",
|
||||||
|
"session": "demo",
|
||||||
|
"expectExact": "REMEMBERED",
|
||||||
|
"timeoutSeconds": 180
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"taskVersion": 1,
|
||||||
|
"id": "t-session-recall",
|
||||||
|
"prompt": "What code word did I ask you to remember earlier in this session? Reply with only the code word.",
|
||||||
|
"session": "demo",
|
||||||
|
"timeoutSeconds": 180
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user