orchestrata_ultimate.core.artifact_store
Reference › orchestrata_ultimate › core › artifact_store
The canonical store for the artifacts that pipeline stages produce, shared by the kernel and the orchestrator. It closes phase 5.1 of SPEC-107 by merging two divergent stores, kernel.pipeline_artifacts and orchestrator.artifact_store, which now build on it. Kernel callers use stage and type; orchestrator callers use stage_alias and artifact_type (an enum or a string). Both spellings are accepted on construction and in queries, and internally the values are plain strings.
PipelineArtifact is a dataclass holding stage, type, content, format, optional file path, timestamp, thread and session ids, spec and requirement ids. It provides filename, to_dict (which writes both naming conventions) and from_dict. ArtifactStore keeps a lock-guarded list. add appends and, when an output_dir is configured, _persist appends the artifact to a JSONL file; on construction _replay reloads earlier artifacts from it, so old orchestrator files and new kernel files are interchangeable. Queries are by_stage, by_type, by_thread, by_requirement and by_spec. render_artifact_digest ranks artifacts by recency and optional query match and renders a digest within an artifact and token budget, using the word token counter from memory.token_counter. write_to_disk flushes artifacts into per-thread directories with extensions chosen by format.
At a glance
| Source | src/orchestrata_ultimate/core/artifact_store.py |
| Lines of code | 515 |
| Classes | 2 |
| Functions | 2 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.memory.token_counter |
| Imported by | orchestrata_ultimate.kernel.pipeline_artifacts, orchestrata_ultimate.orchestrator.artifact_store |
| Tests that import it | tests/generated_core/test_artifact_store_generated.py |
Class diagram
Dependency diagram
API
Classes
PipelineArtifact (dataclass)
Decorators: dataclass, traces(spec='001', story='US-006', reqs=['REQ-001-US-006-018']) · Line 69
A single artifact produced by a pipeline stage.
| Attribute | Type | Default |
|---|---|---|
stage |
str |
|
type |
str |
|
content |
str |
|
format |
str |
'text' |
file_path |
str | None |
None |
timestamp |
datetime |
field(default_factory=lambda: datetime.now(UTC)) |
thread_id |
str |
'' |
session_id |
str |
'' |
spec |
str |
'' |
reqs |
tuple[str, ...] |
() |
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(stage: str | None = None, stage_alias: str | None = None, type: str | Any | None = None, artifact_type: str | Any | None = None, content: str = '', format: str = 'text', file_path: str | None = None, timestamp: datetime | None = None, thread_id: str = '', session_id: str = '', spec: str = … |
Initialize with either kernel or orchestrator field names. |
stage_alias property |
stage_alias() -> str |
Orchestrator-style alias for :attr:stage. |
artifact_type property |
artifact_type() -> str |
Orchestrator-style alias for :attr:type. |
filename |
filename(index: int = 0) -> str |
Build a stable on-disk filename for this artifact. |
to_dict |
to_dict() -> dict[str, Any] |
Serialise to a JSON-friendly dict with both naming conventions. |
from_dict classmethod |
from_dict(data: dict[str, Any]) -> PipelineArtifact |
Rebuild from a JSON-friendly dict. |
ArtifactStore
Decorators: traces(spec='001', story='US-006', reqs=['REQ-001-US-006-018']) · Line 196
Thread-safe collection of pipeline artifacts, queryable + persistent.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(output_dir: Path | str | None = None) -> None |
Initialise an empty, lock-guarded artifact list. |
output_dir property |
output_dir() -> Path | None |
The configured persistence directory, or None. |
_replay |
_replay() -> None |
Load previously persisted artifacts from JSONL into memory. |
_persist |
_persist(artifact: PipelineArtifact) -> None |
Append one artifact to the JSONL backing store. |
add |
add(artifact: PipelineArtifact) -> None |
Add an artifact to the store and persist it when output_dir is set. |
add_content |
add_content(*, stage: str | None = None, stage_alias: str | None = None, type: str | Any | None = None, artifact_type: str | Any | None = None, content: str, format: str = 'text', file_path: str | None = None, thread_id: str = '', session_id: str = '', spec: str = '', reqs: tuple[str, ...] | list[s… |
Construct and add an artifact from its fields. |
all |
all() -> list[PipelineArtifact] |
Return a snapshot copy of every artifact. |
by_stage |
by_stage(stage: str | None = None, *, stage_alias: str | None = None) -> list[PipelineArtifact] |
Return all artifacts produced by stage. |
by_type |
by_type(type: str | Any | None = None, *, artifact_type: str | Any | None = None) -> list[PipelineArtifact] |
Return all artifacts of a given logical type. |
by_thread |
by_thread(thread_id: str) -> list[PipelineArtifact] |
Return all artifacts belonging to thread_id. |
by_requirement |
by_requirement(req_id: str) -> list[PipelineArtifact] |
Return all artifacts whose reqs include req_id. |
by_spec |
by_spec(spec_id: str) -> list[PipelineArtifact] |
Return all artifacts whose spec equals spec_id. |
count |
count() -> int |
Return the number of artifacts in the store. |
__len__ |
__len__() -> int |
|
to_json |
to_json() -> str |
Return a compact JSON preview of every artifact. |
render_artifact_digest |
render_artifact_digest(query: str | None = None, *, max_artifacts: int = 10, max_tokens: int = 2000) -> str |
Render a ranked markdown digest of the most relevant artifacts. |
_rank_artifacts |
_rank_artifacts(artifacts: list[PipelineArtifact], query: str) -> list[PipelineArtifact] |
Score artifacts by recency + optional keyword relevance. |
write_to_disk |
write_to_disk(output_dir: Path | str) -> list[str] |
Flush every artifact to <output_dir>/<thread_id>/. |
Functions
| Function | Signature | Summary |
|---|---|---|
_coerce_str_type |
_coerce_str_type(value: Any) -> str |
Normalize an enum or string value to a plain string. |
_coerce_reqs |
_coerce_reqs(reqs: Any) -> tuple[str, ...] |
Normalize requirement ids to a tuple of strings. |
Source
"""Canonical artifact store shared by the kernel and orchestrator pipelines.
This module closes Phase 5.1 of SPEC-107: it merges the previously divergent
``kernel.pipeline_artifacts.ArtifactStore`` and
``orchestrator.artifact_store.ArtifactStore`` into a single canonical
implementation while preserving both calling conventions:
* Kernel callers use ``stage`` / ``type`` (plain strings).
* Orchestrator callers use ``stage_alias`` / ``artifact_type`` (``ArtifactType``
enum or plain string).
Both sets of names are accepted at construction and query time; the internal
canonical representation is always ``stage`` and ``type`` strings. The JSONL
persistence format writes both key sets so older orchestrator JSONL files and
newer kernel JSONL files can be replayed interchangeably.
"""
from __future__ import annotations
import json
import logging
import os
import re
import threading
from dataclasses import dataclass, field
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.memory.token_counter import WordTokenCounter
_logger = logging.getLogger(__name__)
# Filename extension per artifact ``format`` so disk output is browsable.
_FORMAT_EXT = {
"python": "py",
"json": "json",
"mermaid": "mmd",
"prolog": "pl",
"markdown": "md",
"text": "txt",
}
def _coerce_str_type(value: Any) -> str:
"""Normalize an enum or string value to a plain string."""
if value is None:
return ""
if isinstance(value, str):
return value
enum_value = getattr(value, "value", None)
if isinstance(enum_value, str):
return enum_value
return str(value)
def _coerce_reqs(reqs: Any) -> tuple[str, ...]:
"""Normalize requirement ids to a tuple of strings."""
if reqs is None:
return ()
if isinstance(reqs, str):
return (reqs,)
return tuple(str(r) for r in reqs)
@dataclass
@traces(spec="001", story="US-006", reqs=["REQ-001-US-006-018"])
class PipelineArtifact:
"""A single artifact produced by a pipeline stage.
This is the canonical value object. It accepts either kernel-style
(``stage`` / ``type``) or orchestrator-style (``stage_alias`` /
``artifact_type``) field names, but stores the data as plain strings
internally.
"""
stage: str
type: str
content: str
format: str = "text"
file_path: str | None = None
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
thread_id: str = ""
session_id: str = ""
spec: str = ""
reqs: tuple[str, ...] = ()
def __init__(
self,
stage: str | None = None,
stage_alias: str | None = None,
type: str | Any | None = None,
artifact_type: str | Any | None = None,
content: str = "",
format: str = "text",
file_path: str | None = None,
timestamp: datetime | None = None,
thread_id: str = "",
session_id: str = "",
spec: str = "",
reqs: tuple[str, ...] | list[str] | str | None = None,
) -> None:
"""Initialize with either kernel or orchestrator field names."""
self.stage = stage or stage_alias or ""
resolved_type = type if type is not None else artifact_type
self.type = _coerce_str_type(resolved_type)
self.content = content
self.format = format
self.file_path = file_path
self.timestamp = timestamp if timestamp is not None else datetime.now(UTC)
self.thread_id = thread_id
self.session_id = session_id
self.spec = spec
self.reqs = _coerce_reqs(reqs)
@property
def stage_alias(self) -> str:
"""Orchestrator-style alias for :attr:`stage`."""
return self.stage
@property
def artifact_type(self) -> str:
"""Orchestrator-style alias for :attr:`type`."""
return self.type
def filename(self, index: int = 0) -> str:
"""Build a stable on-disk filename for this artifact.
Args:
index: Disambiguator when multiple artifacts share stage+type.
Returns:
``<stage>_<type>[_<index>].<ext>`` with the stage alias sanitised
(``U:Ar`` → ``U-Ar``).
"""
if self.file_path:
return os.path.basename(self.file_path)
ext = _FORMAT_EXT.get(self.format, "txt")
stage = self.stage.replace(":", "-").replace("/", "-") or "stage"
suffix = f"_{index}" if index else ""
return f"{stage}_{self.type}{suffix}.{ext}"
def to_dict(self) -> dict[str, Any]:
"""Serialise to a JSON-friendly dict with both naming conventions."""
return {
"stage": self.stage,
"stage_alias": self.stage,
"type": self.type,
"artifact_type": self.type,
"content": self.content,
"format": self.format,
"file_path": self.file_path,
"timestamp": self.timestamp.isoformat(),
"thread_id": self.thread_id,
"session_id": self.session_id,
"spec": self.spec,
"reqs": list(self.reqs),
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> PipelineArtifact:
"""Rebuild from a JSON-friendly dict.
Accepts either kernel-style keys (``stage`` / ``type``) or
orchestrator-style keys (``stage_alias`` / ``artifact_type``).
"""
ts = data.get("timestamp", "")
try:
timestamp = datetime.fromisoformat(ts) if ts else datetime.now(UTC)
except ValueError:
timestamp = datetime.now(UTC)
reqs = data.get("reqs") or []
if isinstance(reqs, str):
reqs = [reqs]
stage = data.get("stage") or data.get("stage_alias") or ""
type_value = data.get("type") or data.get("artifact_type") or ""
return cls(
stage=stage,
type=_coerce_str_type(type_value),
content=data.get("content", ""),
format=data.get("format", "text"),
file_path=data.get("file_path"),
timestamp=timestamp,
thread_id=data.get("thread_id", ""),
session_id=data.get("session_id", ""),
spec=data.get("spec", ""),
reqs=tuple(str(r) for r in reqs),
)
@traces(spec="001", story="US-006", reqs=["REQ-001-US-006-018"])
class ArtifactStore:
"""Thread-safe collection of pipeline artifacts, queryable + persistent.
Add artifacts as stages complete; query them by stage, type, requirement,
or thread; flush the whole collection to disk on demand; and render a
ranked digest within a token budget.
When ``output_dir`` is provided, every added artifact is appended to
``<output_dir>/artifacts.jsonl`` and the file is replayed into the
in-memory index on construction.
"""
def __init__(self, output_dir: Path | str | None = None) -> None:
"""Initialise an empty, lock-guarded artifact list.
Args:
output_dir: Optional directory for JSONL persistence. When given,
existing ``artifacts.jsonl`` is replayed into memory.
"""
self._artifacts: list[PipelineArtifact] = []
self._lock = threading.RLock()
self._output_dir: Path | None = (
Path(output_dir) if output_dir is not None else None
)
self._jsonl_path: Path | None = None
if self._output_dir is not None:
self._output_dir.mkdir(parents=True, exist_ok=True)
self._jsonl_path = self._output_dir / "artifacts.jsonl"
self._replay()
@property
def output_dir(self) -> Path | None:
"""The configured persistence directory, or None."""
return self._output_dir
# ── persistence ───────────────────────────────────────────────────────
def _replay(self) -> None:
"""Load previously persisted artifacts from JSONL into memory."""
if self._jsonl_path is None or not self._jsonl_path.exists():
return
loaded = 0
with open(self._jsonl_path, encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
self._artifacts.append(PipelineArtifact.from_dict(data))
loaded += 1
except (json.JSONDecodeError, TypeError, KeyError) as exc:
_logger.warning("Skipping malformed artifact JSONL line: %s", exc)
_logger.debug("Replayed %d artifact(s) from %s", loaded, self._jsonl_path)
def _persist(self, artifact: PipelineArtifact) -> None:
"""Append one artifact to the JSONL backing store."""
if self._jsonl_path is None:
return
try:
with open(self._jsonl_path, "a", encoding="utf-8") as fh:
fh.write(json.dumps(artifact.to_dict(), ensure_ascii=False))
fh.write("\n")
except OSError as exc:
_logger.warning(
"Artifact persistence failed (%s): %s", self._jsonl_path, exc
)
# ── mutation ──────────────────────────────────────────────────────────
@traces(spec="001", story="US-006", reqs=["REQ-001-US-006-018"])
def add(self, artifact: PipelineArtifact) -> None:
"""Add an artifact to the store and persist it when output_dir is set."""
with self._lock:
self._artifacts.append(artifact)
self._persist(artifact)
_logger.debug(
"[Artifact] %s/%s (%s) thread=%s",
artifact.stage,
artifact.type,
artifact.format,
artifact.thread_id,
)
@traces(spec="001", story="US-006", reqs=["REQ-001-US-006-018"])
def add_content(
self,
*,
stage: str | None = None,
stage_alias: str | None = None,
type: str | Any | None = None,
artifact_type: str | Any | None = None,
content: str,
format: str = "text",
file_path: str | None = None,
thread_id: str = "",
session_id: str = "",
spec: str = "",
reqs: tuple[str, ...] | list[str] | None = None,
) -> PipelineArtifact:
"""Construct and add an artifact from its fields.
Accepts either kernel-style (``stage`` / ``type``) or orchestrator-style
(``stage_alias`` / ``artifact_type``) field names.
Returns:
The artifact that was added.
"""
resolved_stage = stage or stage_alias or ""
resolved_type = type if type is not None else artifact_type
artifact = PipelineArtifact(
stage=resolved_stage,
type=_coerce_str_type(resolved_type),
content=content,
format=format,
file_path=file_path,
thread_id=thread_id,
session_id=session_id,
spec=spec,
reqs=tuple(reqs) if reqs is not None else (),
)
self.add(artifact)
return artifact
# ── queries ───────────────────────────────────────────────────────────
def all(self) -> list[PipelineArtifact]:
"""Return a snapshot copy of every artifact."""
with self._lock:
return list(self._artifacts)
def by_stage(
self, stage: str | None = None, *, stage_alias: str | None = None
) -> list[PipelineArtifact]:
"""Return all artifacts produced by *stage*."""
target = stage or stage_alias or ""
with self._lock:
return [a for a in self._artifacts if a.stage == target]
def by_type(
self, type: str | Any | None = None, *, artifact_type: str | Any | None = None
) -> list[PipelineArtifact]:
"""Return all artifacts of a given logical *type*."""
target = _coerce_str_type(type if type is not None else artifact_type)
with self._lock:
return [a for a in self._artifacts if a.type == target]
def by_thread(self, thread_id: str) -> list[PipelineArtifact]:
"""Return all artifacts belonging to *thread_id*."""
with self._lock:
return [a for a in self._artifacts if a.thread_id == thread_id]
def by_requirement(self, req_id: str) -> list[PipelineArtifact]:
"""Return all artifacts whose ``reqs`` include *req_id*."""
with self._lock:
return [a for a in self._artifacts if req_id in a.reqs]
def by_spec(self, spec_id: str) -> list[PipelineArtifact]:
"""Return all artifacts whose ``spec`` equals *spec_id*."""
with self._lock:
return [a for a in self._artifacts if a.spec == spec_id]
def count(self) -> int:
"""Return the number of artifacts in the store."""
with self._lock:
return len(self._artifacts)
def __len__(self) -> int:
with self._lock:
return len(self._artifacts)
# ── serialization preview ─────────────────────────────────────────────
def to_json(self) -> str:
"""Return a compact JSON preview of every artifact.
The output schema is preserved for backward compatibility:
``stage``, ``type``, ``content_preview``, ``timestamp``.
"""
with self._lock:
return json.dumps(
[
{
"stage": a.stage,
"type": a.type,
"content_preview": a.content[:200],
"timestamp": a.timestamp.isoformat(),
}
for a in self._artifacts
],
indent=2,
default=str,
)
# ── ranked digest ─────────────────────────────────────────────────────
@traces(spec="107", reqs=["REQ-107-032"])
def render_artifact_digest(
self,
query: str | None = None,
*,
max_artifacts: int = 10,
max_tokens: int = 2000,
) -> str:
"""Render a ranked markdown digest of the most relevant artifacts.
Ranking blends recency and, when ``query`` is supplied, keyword overlap
with the artifact's stage/type/content/spec/requirements. The digest
stops once ``max_tokens`` is reached.
Args:
query: Optional free-text query to bias ranking.
max_artifacts: Maximum artifacts to include.
max_tokens: Token budget for the rendered digest.
Returns:
Markdown digest, or a short message when the store is empty.
"""
artifacts = self.all()
if not artifacts:
return "_No artifacts yet._"
ranked = self._rank_artifacts(artifacts, query or "")
counter = WordTokenCounter()
lines: list[str] = ["# Artifact Digest"]
spent = counter.count(lines[0])
count = 0
now = datetime.now(UTC)
for artifact in ranked[:max_artifacts]:
age_hours = (now - artifact.timestamp).total_seconds() / 3600.0
age_note = (
f"{age_hours:.1f}h ago"
if age_hours < 48
else f"{age_hours / 24:.1f}d ago"
)
reqs_note = f" [{', '.join(artifact.reqs)}]" if artifact.reqs else ""
spec_note = f" (spec:{artifact.spec})" if artifact.spec else ""
block = (
f"## {artifact.stage} / {artifact.type}{spec_note}{reqs_note}\n"
f"- thread: `{artifact.thread_id or '-'}` | {age_note}\n"
f"```\n{artifact.content[:800]}\n```"
)
block_tokens = counter.count(block)
if spent + block_tokens > max_tokens and count > 0:
break
lines.append(block)
spent += block_tokens
count += 1
return "\n\n".join(lines)
def _rank_artifacts(
self, artifacts: list[PipelineArtifact], query: str
) -> list[PipelineArtifact]:
"""Score artifacts by recency + optional keyword relevance."""
if not query.strip():
return sorted(artifacts, key=lambda a: a.timestamp, reverse=True)
terms = [t.lower() for t in re.findall(r"[a-z0-9_]+", query) if len(t) > 2]
now = datetime.now(UTC)
scored: list[tuple[float, PipelineArtifact]] = []
for artifact in artifacts:
haystack = " ".join(
[
artifact.stage,
artifact.type,
artifact.content,
artifact.spec,
" ".join(artifact.reqs),
]
).lower()
matches = sum(1 for term in terms if term in haystack)
relevance = matches / max(len(terms), 1)
age_days = max(0.0, (now - artifact.timestamp).total_seconds() / 86400.0)
recency = 1.0 / (1.0 + age_days)
score = 0.6 * recency + 0.4 * relevance
scored.append((score, artifact))
scored.sort(key=lambda x: (x[0], x[1].timestamp), reverse=True)
return [a for _score, a in scored]
# ── disk flush ────────────────────────────────────────────────────────
@traces(spec="001", story="US-006", reqs=["REQ-001-US-006-018"])
def write_to_disk(self, output_dir: Path | str) -> list[str]:
"""Flush every artifact to ``<output_dir>/<thread_id>/``.
Each artifact's content is written to a file named by
:meth:`PipelineArtifact.filename`, disambiguated by index when a
stage produces several artifacts of the same type. Artifacts with an
empty ``thread_id`` are grouped under ``_no_thread``.
Args:
output_dir: Root directory (``pipeline_output`` by convention).
Returns:
Absolute paths of every file written.
"""
root = Path(output_dir)
written: list[str] = []
with self._lock:
artifacts = list(self._artifacts)
seen: dict[tuple[str, str, str], int] = {}
for artifact in artifacts:
thread = artifact.thread_id or "_no_thread"
target_dir = root / thread
target_dir.mkdir(parents=True, exist_ok=True)
key = (thread, artifact.stage, artifact.type)
index = seen.get(key, 0)
seen[key] = index + 1
path = target_dir / artifact.filename(index)
try:
path.write_text(artifact.content, encoding="utf-8")
written.append(str(path))
except OSError as exc:
_logger.warning("Artifact write failed (%s): %s", path, exc)
return written