Skip to content

Releases: Particle-Academy/prism-harness-py

v0.4.2

Choose a tag to compare

@github-actions github-actions released this 29 Oct 23:19

FileSessionStore writes survive a concurrent reader on Windows

  • Fixed: on Windows, saving session state failed with PermissionError (WinError 5) whenever another process had the
    state file open, because Windows will not replace an open file. A write now retries the replacement only on Windows
    permission errors, for up to 750 ms, and then raises the original error. On failure the previous state file stays
    intact and the temporary file is removed; nothing is written partially or dropped silently. On Windows, state reads
    open the file with delete sharing and hold it only for the read itself.
  • Other platforms are unchanged.

No breaking changes.

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 29 Oct 17:42

narrow_requested_tools now applies to legacy sessions

  • Fixed: in 0.4.0, AgentRuntime(narrow_requested_tools=True) had no effect on legacy (no-agent) sessions, so a
    caller-supplied tool list could still offer tools beyond the mode's list. With the option on, a legacy turn now
    offers only tools in the mode's list. A requested tool outside it raises tool_not_available, "*" means the mode's
    list, and an empty list offers no tools.
  • With the option off (the default), legacy behaviour is unchanged from 0.3.0, including an empty list falling back to
    the mode's tools. Named agents were already narrow-only.

No breaking changes.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 14:59
3857aeb

Agent-scoped harness and an owner-proof file lock

Agents can now share one application without colliding. Existing applications keep their current behaviour, except
for the file lock format described under BREAKING CHANGE.

  • PrismHarness.for_agent("support") selects an isolated agent namespace for session keys, threads, task lists, tools,
    modes and authorization. Choose a stable id; never derive it from a display name.
  • In a named namespace, duplicate tool and mode names raise RegistrationCollision (registration_collision), naming
    both owners. Replacement is explicit (replace / replace_factory / replace_provider). A caller-supplied tool list
    can only narrow the mode, and built-ins are offered only when a mode lists them.
  • AgentHarness.effective_tools(mode, participant, scope) and AgentRuntime.effective_tools(session, mode) return the
    exact offered tool set, with each tool's owner and source. They run no handler, send no model request and create no
    session or index entry. Expired entries may be purged, as any read does.
  • ParticipantRef and ParticipantAdapters identify non-user participants, and PendingSession.as_actor() binds a host
    actor. A named subagent tool accepts only task, and a child cannot switch actor.
  • Opt-in hardening for the legacy namespace, off by default:
    • refuse_duplicates=True refuses duplicate legacy registrations instead of overwriting them;
    • narrow_requested_tools=True on AgentRuntime stops a caller-supplied tool list from adding tools beyond the
      mode's list.
      Without narrow_requested_tools, a caller-supplied list can still widen a legacy mode, and an empty list still falls
      back to the mode's tools, as before; enabling it is recommended.
  • max_addresses_per_participant (default unlimited) caps indexed addresses with AddressLimitExceeded
    (address_limit_exceeded), raised before any write. Indexed namespaces require a store whose lock covers its sharing
    scope (index_lock_scope_unsupported). The memory store and the file store both qualify.
  • FileSessionStore.pending_releases() and identity_status() report a delayed lock release or unavailable host
    identity, without doing any I/O.

BREAKING CHANGE: FileSessionStore locks are now owner-proof directories instead of timestamp files. A lock is
reclaimed only when its owner is provably dead on the same host, boot and process space, never just because its TTL
passed. STOP ALL WORKERS running 0.3.x or earlier (Python or TypeScript) before upgrading: no rolling upgrade is
supported. A leftover old lock file is refused with session_locked and the detail legacy_lock_requires_maintenance,
naming the path to inspect and archive. When host identity cannot be established, locking still works but crash
recovery waits for maintenance. On Windows the first lock in a process starts one fixed, read-only PowerShell metadata
query. Local filesystems only: SMB and NFS are unsupported.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 27 Oct 22:06

A failed model step is terminal

Before this release, when a model response finished with 'error' or 'unknown', the runtime still invoked the tool calls returned with it and took another model step.

  • A response that finishes with 'error' or 'unknown' is recorded (assistant message, one step, its cost) and the run then fails with the new error code model_step_failed, carrying the bounded finish_reason ('error' | 'unknown'). The message never includes model text.
  • No tool call from the failed response is invoked, and a later approval resume cannot make those calls dispatchable; they are recorded as refused.
  • Valid stop and tool-call continuation is unchanged.

BREAKING CHANGE: a run whose model step failed now fails with ErrorCode model_step_failed instead of continuing or invoking that step's tools. Callers that relied on tools running after an error response will no longer see them run.

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 07 Oct 19:16

v0.2.1 - Five README examples that could not run

No breaking changes.

No source changed at all; this release is the package page.

ModeRegistry, HarnessError, record_approval and TaskOutcome were used in
examples with no import line. All four are in prism_harness's all, so a
reader copying one of those blocks got a NameError on a name this package
exports -- which reads as a missing feature rather than a missing line of
documentation. A fifth block was a bare dict entry rather than a statement,
which Python will not parse.

Released as a patch rather than left for the next feature because the README IS
the PyPI page: the broken examples were being served to everyone who looked.

TaskOutcome is worth a note for anyone comparing the ports. The TypeScript
README's equivalent line passes the string 'done' and needs no import, because
TaskOutcome there is a union of string literals that exists at compile time
only. Here it is an enum, and converting through it at the call site is what
makes this port and the PHP reference refuse an outcome that is not one of the
four before anything reaches the durable list. The two READMEs read differently
on purpose.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 03:16

v0.2.0 - Structured turns

A turn whose answer is a document rather than prose.

response = runtime.send_structured(session, "Plan the release", schema)

response.structured  # {"title": "Ship it", "steps": ["write", "test"]}
response.text        # the document as the model wrote it

It is the same run as send() — the mode's system prompt, its tools, the step
budget, the events, the approvals. Your client receives the schema as
request.schema and returns what it parsed as LlmResponse.structured.

The assistant message is the raw document, and the parsed object rides along in
the row's additional_content under structured. A later turn replays the
conversation as messages and reads the text, so a thread holding a structured
answer reads like any other — and thr-0011..0013 in prism-parity's
harness-thread-rows corpus pin that shape across PHP, TypeScript and Python.

  • structured_unreadable — the text held no document at all
  • structured_schema_violation — a document with the wrong shape, carrying
    problems: every way it missed, not the first

Nothing is coerced and nothing is trimmed to the fields that fit. An empty plan
settles a batch as done, which reads exactly like a considered answer of
"nothing to propose". The refused exchange is still recorded and the run marked
failed.

schema_problems() is exported if you want the check directly. It reads the
JSON Schema — declared types, required keys, enum members, array items, and keys
nobody declared where a schema closes itself — and passes $ref, allOf,
oneOf and the numeric and string facets rather than half-implementing them.
The same rules as the PHP reference, message for message.

True is not a number here: bool subclasses int, so an unguarded check
accepts true where a schema asks for a number, and PHP and TypeScript refuse
it without being asked. And a whole float reads as the integer JSON writes —
2.0 is 2 in every JSON document — rather than as repr() spells it. Both
are tested.

An error carrying a document now records its CODE as the run's failure, not its
message. A violation message names the values that missed, so the old behaviour
would have written pieces of the model's document into the run row and into
every listener's RunFailed event — in a package that keeps tool arguments out
of events on purpose.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 07:29

v0.1.0 - First release on PyPI

Durable agent sessions for Python: threads, session state and store drivers. The Python port of particle-academy/prism-harness. The distribution is prism-ai-harness; the package imports as prism_harness.

pip install prism-ai-harness

What it has

  • Sessions rebuilt from a store on every call. The ephemeral slot holds the active mode, selected model and run bookkeeping; the durable slot holds threads and stored capabilities.
  • A store that reports itself volatile is refused for the durable slot.
  • MemorySessionStore (volatile) and FileSessionStore (durable: atomic writes and a cross-process lock).
  • Session.key() is byte for byte the key the PHP reference builds, so a PHP app and a Python agent can share one store.
  • Threads store the rows the PHP reference stores. Positions are assigned inside the store lock, so concurrent turns do not overwrite each other.
  • Attachments on a turn. One given by URL or file path, one that is not media, or one with nothing in it is refused with a HarnessError code.
  • Approvals. A mode names the tools a person must approve. Record a decision with record_approval() and resume: approved calls run once, denied calls return their reason, and a call with no decision is refused.
  • Task lists with leases. Only the worker holding a task can release it, and an agent cannot mark its own task done.
  • No runtime dependencies. Python 3.10 or later. The store contract is synchronous.

prism-parity's corpora pin the session key, thread rows, turn attachments and task claims against the PHP reference and the TypeScript port.