Artifacts & resources¶
What it is / when to use it¶
PenguiFlow supports artifacts to keep large/binary data out of LLM context while still making it available to:
- UIs (downloads, previews, rich components),
- downstream systems (storage, audits),
- later steps in a workflow.
Use artifacts when tools produce:
- files generated by tools,
- structured UI outputs,
- references passed between steps without inlining large payloads.
Artifacts are also the mechanism behind many MCP resource workflows (see MCP resources).
Non-goals / boundaries¶
- Artifacts are not an access-control system by themselves.
ArtifactScopeis metadata; enforcement is the host/app responsibility. - Artifacts are not guaranteed durable unless your ArtifactStore is durable and retention/TTL permits it.
- Do not store secrets in artifacts unless you apply the same controls you would for any sensitive blob store.
Contract surface¶
Core runtime types¶
Artifacts live in penguiflow.artifacts:
ArtifactStoreprotocol (put_bytes,put_text,get,get_ref,exists,delete)ArtifactRef(the only thing that should enter LLM context)ArtifactRetentionConfig(TTL + size/count limits)
Planner integration¶
ReactPlanner can use an artifact store in three ways:
- explicit:
ReactPlanner(..., artifact_store=...) - discovered: if
state_storeexposesartifact_store(duck-typed) or implementsArtifactStore - fallback:
NoOpArtifactStore(artifacts become “no-op” references)
When the planner stores an artifact, it emits:
PlannerEvent(event_type="artifact_stored", extra={artifact_id, mime_type, ...})
Tool outputs: marking artifact fields¶
If a tool output has a field that must not be inlined into LLM context, mark it:
from pydantic import BaseModel, Field
class ToolResult(BaseModel):
summary: str
binary_blob: str | None = Field(default=None, json_schema_extra={"artifact": True})
The planner’s artifact collector extracts these fields into trajectory.artifacts and can redact them from the LLM-visible observation.
ToolNode integration (automatic extraction)¶
ToolNode has its own artifact extraction pipeline configured via:
ExternalToolConfig.artifact_extraction
It can:
- clamp large inline strings into
ctx._artifacts.put_text(...)(internal) - extract binary content into
ctx._artifacts.put_bytes(...)(internal) - turn MCP
resource_linkblocks into “stubs” that point to resources tooling
Note:
ctx._artifactsis the rawArtifactStoreused by penguiflow internals. Tool developers should usectx.artifacts(theScopedArtifactsfacade) which providesupload()/download()/list()with automatic scope injection.
Operational defaults¶
Retention defaults (start here)¶
- set a TTL that matches your UX (“how long should a user be able to download this?”)
- cap per-artifact and per-session size
- use
cleanup_strategy="lru"unless you have a strong reason
Extraction defaults (start safe)¶
- keep
max_inline_sizeconservative to avoid prompt bloat - prefer returning summaries + artifact refs rather than full payloads
- for multi-tenant systems: always populate artifact scope (session/tenant/user) at the app boundary
Failure modes & recovery¶
Artifacts missing/expired¶
Symptoms
- UI cannot fetch an artifact by id
Likely causes
- TTL expired
- size/eviction limits removed it
- using
NoOpArtifactStore
Fix
- increase TTL / retention limits
- use a durable ArtifactStore in production
- store durable references in your own storage layer when needed
Prompt bloat despite artifacts¶
Likely causes
- tool outputs are not marked with
json_schema_extra={"artifact": True} - ToolNode extraction is not configured/tuned
Fix
- mark artifact fields in tool outputs
- lower
max_inline_sizeand ensure large strings become artifacts
Observability¶
Record at minimum:
- artifact counts and sizes by session/trace
artifact_storedevents (rate + size distribution)- artifact store eviction/TTL behavior (implementation specific)
Security / multi-tenancy notes¶
ArtifactRefis safe to show to the LLM; raw bytes/base64 are not.- Treat artifact ids as sensitive identifiers if your download endpoint is publicly reachable.
- Use scoped ids and enforce access checks on retrieval endpoints.
Runnable example: artifact store for planner runs¶
from penguiflow.artifacts import ArtifactRetentionConfig, InMemoryArtifactStore
from penguiflow.planner import ReactPlanner
artifact_store = InMemoryArtifactStore(
retention=ArtifactRetentionConfig(
ttl_seconds=3600,
max_artifact_bytes=50_000_000,
max_session_bytes=500_000_000,
cleanup_strategy="lru",
)
)
planner = ReactPlanner(
llm="gpt-4o-mini",
catalog=catalog,
artifact_store=artifact_store,
)
Troubleshooting checklist¶
- Artifacts never appear: ensure you’re not using
NoOpArtifactStoreand your tool outputs mark artifact fields (or ToolNode extraction is enabled). - Artifacts leak across sessions: enforce
ArtifactScopeat your host boundary and avoid shared download URLs. - Large payloads still inline: tune
max_inline_sizeand mark fields withjson_schema_extra={"artifact": True}.