Addresses merge-gate findings on #6251:
- the manifest no longer writes the user prompt to disk in plaintext; it
keeps a SHA-256 digest (run_digest) used only to recognise retries
- revert SessionManagerMixin._save/_load_session_state to main: no callers,
so the new plaintext state write had no live consumer
- tests: prompt never reaches disk; absolute/backslash/'..' ids stay inside
the manifest directory
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AgentFlow normalises an Agent step to Task(agent=...), so editing the
agent's instructions or model under the same name was served a stale
durable result. Include the wrapped agent's config and agent_config.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Encode set/frozenset constants (and set-valued variables) in sorted order so
a key no longer depends on PYTHONHASHSEED and durable resume hits.
- Step definition now includes immutable defaults/closure values, module-level
Python helpers the handler calls, and an agent's role/goal/instructions/llm/
tool names, so a same-name edit misses instead of serving a stale result.
- Docstring states what is not covered; tests edit steps under the same name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- manifest filenames use the conversation store's injective naming (a:b vs a_b)
- end_run(run_id=...) leaves another run's marker alone; read-modify-write is
guarded by the store's cross-process FileLock
- praisonai run scopes manifests under the project sessions dir
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Step keys include a fingerprint of the handler's code, so an edited
handler kept under the same name is not served its old result.
- set() rejects values that change in a JSON round trip (int dict keys,
tuples) instead of resuming with different data.
- set() uses the locked update_json read-modify-write so two caches on
one path no longer erase each other's entries.
- Docstring now states that agent steps are keyed by name only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adds a per-session manifest that stores references to each of a session's
storage areas, plus a write-ahead run-in-flight marker with a limit on retry
attempts. Replaces the session_manager.py placeholders with code that uses the
manifest, and makes `praisonai run --continue/--session` report a run that was
interrupted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- safe_join now percent-encodes names (bijective), so distinct names such as
'a/b' and 'a_b~c14cddc033f6' can no longer share one file; list_todos()
decodes filenames so listed names round-trip through load/delete_todo.
- Legacy <name>.json fallback skips names containing '%' to avoid aliasing.
- Tests restoring the approval backend snapshotted get_backend(), whose
ConsoleBackend fallback was then installed globally on restore and leaked
into tests/unit/skills/test_approval_scoping.py under xdist. Snapshot
_global_backend directly instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- utils/atomic_io.safe_join: injective, separator-free filename stems
(same scheme as session store); legacy safe names are unchanged.
- PlanStorage save/load/delete_todo and save_plan use safe_join, so
names like '../../x' can no longer escape todos_dir/plans_dir.
- ContextAgent creates PRPs/ and writes PRPs atomically via safe_join.
- generate_title and crawl4ai_*_sync use run_coroutine_from_any_context,
so they work inside a running event loop instead of crashing or
silently returning the fallback.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
_prune_denied_tools drops denied tools from the payload, so the hint must
not name them. Tests now record the formatted tool payload, which also
checks that a nested edit schema is actually sent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Addresses review on #6229: run --agent with llm.base_url probed the env/default
host instead of the server the agent actually calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Gate explicit ollama/ and ollama_chat/ models on the server answering
/api/tags (OLLAMA_API_BASE, OLLAMA_HOST, or 127.0.0.1:11434) instead of
requiring OLLAMA_HOST as a credential.
- Unreachable Ollama now says to start it with `ollama serve` rather than
asking for an API key.
- Raise the local probe timeout from 0.15s to 1s; a running Ollama can take
~0.35s on its first call.
- Only print the 'would be used automatically' hint when auto-detection
actually ran; name the model when one was given.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- API/WebSocket turns charge the budget to a stable caller identity
(bearer-token digest or peer IP) so a new session id cannot reset it.
- gateway.per_user_per_minute changes now take effect on config reload
(validated before cutover; restored on rollback).
- Fractional/boolean/negative budgets are rejected instead of truncated.
- Stale-identity pruning runs at most every 5s instead of on every request.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Wires the existing core SlidingWindowRateLimitPolicy into the shared
AdmissionGate so a single user cannot start unbounded turns. Configured
via gateway.per_user_per_minute (YAML) or BotOS(per_user_per_minute=...);
default 0 keeps ingress unlimited.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- port_utils: serialise claim with flock + exclusive link()/O_EXCL so racing starts cannot both win
- key state lock on ~/.praisonai (where the stores actually live), not PRAISONAI_HOME
- server: acquire state lock in start_with_config before start_channels (journal replay)
- server: release both locks on any failure after claim, not only serve() errors
Co-Authored-By: Claude Opus 5.5 <[email protected]>
os.umask() is process-wide, so the read-and-restore window could make files
created by other threads world-writable. The temp file is now created via
os.open(O_CREAT|O_EXCL, 0o666) so the kernel applies the current umask.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- One process-wide loopback callback server (ref-counted) instead of each
flow binding 127.0.0.1:19876, so concurrent flows no longer collide; the
state-keyed OAuthCallbackHandler routes codes per flow.
- When no browser can be opened, print the authorization URL to stderr so
the operator can consent from any device, and on a TTY accept the pasted
redirect URL (state-checked) to complete the flow.
- InteractiveAuthRequired now points to `praisonai mcp auth <name>`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The quick_ratio() prefilter is a pure-Python pass over each window and was
not charged to the comparison budget, so a failed edit on a 20k-line file
could spend ~4s (with the file lock held) before returning the hint.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Similarity ties could pick an unrelated site containing new_string and
report the edit as applied while the intended target was unchanged.
The closest-match hint already shows retried edits, so keep only that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Only report 'No change needed' when the unique new_string sits inside the
region most similar to old_string; unrelated occurrences elsewhere now
return the normal no-match error with a hint.
- Skip hint for old_string > 2000 chars and cap total SequenceMatcher work so
long/minified lines cannot stall failed edits while file locks are held.
- Truncate each displayed hint line to 200 chars.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- edit_file: when old_string is absent but new_string occurs exactly once
(and is not a substring of old_string), report a no-op success
instead of 'String not found' so retried edits do not loop.
- edit_file / apply_patch: on a miss, include the most similar region
of the file with line numbers so the agent can fix old_string
without re-reading the whole file. Falls back to the existing
concise error when nothing is similar enough.
Stdlib difflib only; no new params, modules or exports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every praisonai.* exception subclassed stdlib Exception/RuntimeError/
ValueError/TimeoutError directly, so callers could not catch "any
PraisonAI error" with one except, despite core's
praisonaiagents.PraisonAIError documenting itself as the base for all
PraisonAI errors.
Re-parent the 15 root wrapper exceptions on a private _WrapperError
(a PraisonAIError subclass). Stdlib parents are kept via multiple
inheritance, and str(e)/args/custom attributes are unchanged, so
existing except sites and CLI messages behave exactly as before.
No new public exports: users catch the existing
praisonaiagents.PraisonAIError.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Agent._save_output_to_file now routes through utils.output_file.write_output_file
(O_NOFOLLOW + hardlink-safe), matching AgentTeam/Workflow.
- FileSnapshot._run_git and GitWorktreeAdapter._git get a 60s timeout and
GIT_TERMINAL_PROMPT=0; worktree treats a timeout as git unavailable.
- BaseStore._archive writes atomically under a FileLock and quarantines a
corrupt archive so eviction keeps working.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Move three opt-in-only imports off module top level:
- agent/execution_mixin.py: _server_registry now imported in the launch
path and cleanup_launch_registration().
- goal/loop.py: judge_goal/_tail imported inside _goal_gate().
- session/__init__.py: EncryptedSessionStore & co. served via __getattr__.
Tests now patch praisonaiagents.goal.judge.judge_goal (the lookup site).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/stop only sets the InterruptController flag and does not cancel an
in-flight send(), so remaining media uploads now re-check liveness and
stop fail-closed, reporting the unsent attachments as skipped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
BotOutboundMessenger accepts an optional liveness predicate (same contract
as ApprovalRequest.liveness) and drops send/react/create_thread/edit/delete
fail-closed when the originating turn was stopped or superseded. The
session binds it to the run's per-turn InterruptController, so a retained
messenger handle from a cancelled turn cannot reach a user channel while
normal post-turn background deliveries keep working.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Merge tool_stats inside the store's locked read-modify-write so
concurrent CLI saves don't drop counts.
- Restore the drained tally when the write fails so a later flush retries.
- Record last_at/last_error_at per tool and pick latest values by tool
time, not session updated_at.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tally per-tool calls/errors/latency from the existing AFTER_TOOL hook
payload, persist it as session `tool_stats` metadata alongside token/cost
in accumulate_session_usage, and add a `tool` grouping (table + --json)
to `praisonai usage`. Core change is limited to surfacing `tool_stats` in
list_sessions. No new Agent params, exports or dependencies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- RAG.astream runs retrieval/rerank via asyncio.to_thread and pumps the sync
LLM stream fallback from a worker thread (no double retrieval)
- RAG.aretrieve/aquery use asyncio.to_thread so contextvars propagate
- EscalationPipeline runs agent.chat in a worker thread and passes heuristic
tools per call instead of mutating the shared agent.tools
- SubQuestionEngine.aquery offloads the sync query (LLM decomposition)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The gateway_stop deadline bound cancels HookRunner.execute via
asyncio.wait_for; the command hook's child process was left running.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each entry {command, events, timeout} registers a core CommandHook on the
default hook registry, so lifecycle events (session_end, message_undelivered,
job_completed, gateway_stop, ...) reach a shell command with the event JSON
on stdin. Reload-safe, skipped in safe mode, schema-validated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Wire praisonaiagents.push.policy.evaluate into DeliveryRouter.deliver via a
small NotificationGate configured from gateway.notifications (YAML) or
DeliveryRouter(notifications=...) (Python). Scheduled results are tagged
agent_finished so categories, quiet hours (recipient timezone) and detail-level
redaction now apply. Unconfigured routers and uncategorised sends are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>