Skip to content

symfonic.capabilities.knowledge.factory

factory

Knowledge as something a composition root can compose.

The decision this file records: knowledge is not its own capability. The package exports four sources -- retrieval, stored documents, attachments and static text -- and every one of them has read. That is the same shape persona_sources and guardrail_sources return, which means knowledge already had a seam into a turn and it is the prompting capability's:

Agent(provider, capabilities=[
    PromptingCapability(sources=[
        *persona_sources(persona),
        *knowledge_sources(retriever=my_retriever, query=question),
    ])
])

Writing a KnowledgeCapability with its own contribute() would have declared a second prompt-assembly stage that competes with the compiler for the same budget, to deliver contributions the compiler already knows how to render. The gap was never a missing capability; it was a missing function that builds the sources.

Trust. These sources declare their payload untrusted on the read, not on the source, so a document is wrapped in the untrusted-data markers rather than rendered as instruction. as_contributions defaults an unlabelled source to a learned tier for exactly this reason -- a retrieved document read to the model as though the deployment had written it is the prompt-injection path, arrived at by omission.

knowledge_sources

knowledge_sources(*, retriever: Any = None, query: str = '', store: Any = None, document_ids: Sequence[str] = (), attachments: Sequence[Any] = (), extractor: Any = None, context: str = '', **policies: Any) -> tuple[Any, ...]

The sources this deployment's knowledge reaches the prompt through.

Every argument is optional and each one adds a source only when it can actually produce something -- a retriever with no query retrieves nothing, and a store with no ids reads nothing. Returning a source that cannot contribute would cost the compiler a budget slot and a delimiter for an empty block, which is the same rule compose follows in governance: leave it out rather than in-but-inert.

Parameters:

Name Type Description Default
retriever Any

a KnowledgeRetriever for similarity search.

None
query str

what to retrieve. Required for retriever to be used.

''
store Any

a DocumentStore to read pinned documents from.

None
document_ids Sequence[str]

which documents to pin into the prompt.

()
attachments Sequence[Any]

refs the caller sent with the turn.

()
extractor Any

turns an attachment into text. Required for attachments.

None
context str

static text this deployment always wants present.

''
**policies Any

policy for retrieval, document_policy for the store, limits for extraction -- each forwarded to its own source, and each already carrying a default.

{}
Source code in src/symfonic/capabilities/knowledge/factory.py
def knowledge_sources(
    *,
    retriever: Any = None,
    query: str = "",
    store: Any = None,
    document_ids: Sequence[str] = (),
    attachments: Sequence[Any] = (),
    extractor: Any = None,
    context: str = "",
    **policies: Any,
) -> tuple[Any, ...]:
    """The sources this deployment's knowledge reaches the prompt through.

    Every argument is optional and each one adds a source only when it can
    actually produce something -- a retriever with no query retrieves nothing,
    and a store with no ids reads nothing. Returning a source that cannot
    contribute would cost the compiler a budget slot and a delimiter for an
    empty block, which is the same rule ``compose`` follows in governance:
    leave it out rather than in-but-inert.

    Args:
        retriever: a ``KnowledgeRetriever`` for similarity search.
        query: what to retrieve. Required for ``retriever`` to be used.
        store: a ``DocumentStore`` to read pinned documents from.
        document_ids: which documents to pin into the prompt.
        attachments: refs the caller sent with the turn.
        extractor: turns an attachment into text. Required for ``attachments``.
        context: static text this deployment always wants present.
        **policies: ``policy`` for retrieval, ``document_policy`` for the
            store, ``limits`` for extraction -- each forwarded to its own
            source, and each already carrying a default.
    """
    sources: list[Any] = []

    if retriever is not None and query:
        sources.append(
            _built(KnowledgeSource, retriever=retriever, query=query,
                   policy=policies.get("policy"))
        )
    if store is not None and document_ids:
        sources.append(
            _built(DocumentSource, store=store, document_ids=tuple(document_ids),
                   policy=policies.get("document_policy"))
        )
    if attachments and extractor is not None:
        sources.append(
            _built(AttachmentSource, refs=tuple(attachments), extractor=extractor,
                   limits=policies.get("limits"))
        )
    if context.strip():
        sources.append(StaticContextSource(text=context.strip()))

    return tuple(sources)