Skip to content

RFC: dedicated agent content API for stable named memory carriers #1251

@zhangMINGkeq1

Description

@zhangMINGkeq1

RFC: Dedicated Agent Content API for Stable Named Memory Carriers

Status

Draft

Summary

OpenViking currently supports session-based memory extraction, but for memory categories such as CASES and PATTERNS, the extracted output is written as randomly named fragment files (e.g. mem_{uuid}.md) under a parent directory.

This works for raw memory extraction, but does not support a common integration need: maintaining a stable, predictably named memory carrier file for long-lived, project-level cumulative memory.

This RFC proposes adding a dedicated agent content API that allows official creation and mutation of stable named memory carrier files under agent-scoped memory paths, without coupling that behavior to session extraction internals.


Problem

Current behavior

In the current session memory extraction flow, extracted memories for normal categories are written to random UUID-based files under the target parent directory.

Observed behavior:

  • Session flow completes successfully
  • Extracted content is persisted as random fragment files
  • A fixed expected URI such as distilled-<project>.md is never materialized
  • Public Session API does not expose a way to specify a target output URI

Why this is insufficient

For many long-lived integrations, especially project-oriented memory accumulation, random fragment files are not a full substitute for a stable named carrier file.

A stable carrier file is useful for:

  • Deterministic URI references
  • Idempotent adapter writes
  • Project-level cumulative memory
  • Cross-session and cross-workflow stable attachment points
  • External tooling that expects a durable logical memory endpoint

Scope of the gap

This is not merely a missing parameter on one API. It is a contract gap between:

  • Memory extraction behavior
  • AGFS resource semantics
  • External adapter expectations
  • Stable memory accumulation workflows

Evidence

1. Session extraction currently uses random filenames

Server-side extraction logic for ordinary extracted memories generates UUID-based filenames and writes directly to parent memory directories, rather than targeting a stable named file.

See Appendix A.1 for source reference.

2. Stable named merge behavior already exists for some categories

Within the same codebase, categories such as TOOLS and SKILLS already use stable filenames and merge/update semantics.

This is strong prior art that:

  • Stable named files are compatible with OpenViking's architecture
  • Merge-oriented memory writing is already an accepted pattern
  • The limitation is not conceptual impossibility

See Appendix A.2 for source reference.

3. Underlying storage primitives already support the needed write shape

At the storage layer, functions such as write_file() and append_file() already support writing to agent-scoped URIs, including create-if-missing style behavior.

This suggests the main gap is not in storage capability, but in the exposed API and resource contract.

See Appendix A.3 for source reference.

4. Existing error text suggests a missing official interface

Some current error messaging already points users toward a "dedicated interface" for adding agent-scope content, but no such public interface is currently discoverable in the documented/public API surface.

See Appendix A.4 for source reference.


Design Goals

The proposed solution should:

  1. Allow official creation of stable named agent memory carrier files
  2. Allow controlled mutation of these files through explicit modes
  3. Preserve compatibility with existing fragment-based extraction
  4. Avoid over-coupling session extraction with arbitrary resource writes
  5. Provide a clean contract for external adapters and automation systems
  6. Support idempotent and auditable updates

Non-Goals

This RFC does not aim to:

  • Replace the existing session extraction model
  • Force all memory categories into stable carrier files
  • Remove fragment-based memory extraction
  • Allow unrestricted arbitrary filesystem mutation without memory semantics
  • Redesign memory categorization as a whole

Proposal

Primary Proposal: Dedicated Agent Content API

Add an official API for creating and mutating agent-scoped memory carrier files.

Example conceptual operations:

  • Create/register a stable named carrier file
  • Append content to an existing carrier
  • Replace content in an existing carrier
  • Merge content into an existing carrier using category-aware strategy

Example conceptual API surface

Create/register carrier

POST /api/v1/agent-content
{
  "agent_id": "<agent-id>",
  "uri": "viking://agent/<agent-id>/memories/patterns/distilled-project.md",
  "category": "PATTERNS",
  "content": "# Initial carrier content",
  "create_mode": "create_if_missing"
}

Mutate carrier

POST /api/v1/agent-content/write
{
  "uri": "viking://agent/<agent-id>/memories/patterns/distilled-project.md",
  "mode": "append",
  "content": "new lesson content",
  "idempotency_key": "<request-id>"
}

Possible mode values:

  • append — add content to the end of the file
  • replace — overwrite the entire file
  • merge — category-aware merge with existing content

Why this is the preferred design

This approach:

  • Keeps session extraction and resource mutation decoupled
  • Makes stable carrier semantics explicit
  • Is easier to reason about than adding arbitrary write-target logic into session commit
  • Aligns with existing hints in current error messaging
  • Gives integrators a predictable and auditable contract

Alternative Options Considered

Option A: Add target_uri to session commit / extraction

This would allow users to direct extracted output into a fixed URI during session extraction.

Why not preferred as the main solution:

  • Couples extraction with resource-writing semantics
  • Makes session commit behavior more complex and less predictable
  • Risks mixing two responsibilities: extraction and persistent file targeting
  • May create unclear merge/overwrite semantics

This could still be considered later as a convenience layer built on top of a dedicated content API.

Option B: Allow content/write to create missing files automatically

This is likely the smallest implementation change, especially if lower-level storage already supports create-if-missing behavior.

Pros:

  • Minimal change
  • Fast to implement
  • Unblocks integrations quickly

Cons:

  • May blur current API semantics
  • Does not clearly establish memory-carrier-specific contract
  • May be too generic for long-term clarity

This is a reasonable short-term bridge, but weaker than a dedicated API as a long-term public contract.


Proposed Semantics

Resource model

A stable memory carrier is:

  • An agent-scoped memory file
  • Predictably named
  • Intended for cumulative updates across time
  • Explicitly managed rather than incidentally created by fragment extraction

Write modes

The API should explicitly support:

  • append: append content to the carrier
  • replace: replace full carrier content
  • merge: category-aware merge into existing content

Idempotency

To support robust external integrations, the API should support one or more of:

  • idempotency_key
  • Revision preconditions
  • Expected existence flags
  • Optimistic concurrency guards

Auditability

Mutation behavior should be observable and auditable:

  • Request mode
  • Timestamp
  • Actor/session identity
  • Whether file was created or updated

Compatibility

This proposal is backward-compatible.

Existing behavior can remain unchanged:

  • Session extraction continues producing fragment files where appropriate
  • Current workflows do not break

The new API simply adds a new official capability for integrations that require stable named carriers.


Migration / Adoption

Typical adoption path for integrators:

  1. Continue using session extraction for raw memory generation if desired
  2. Use the dedicated agent content API to create stable carrier files
  3. Append/merge distilled or curated memory into those stable carriers over time
  4. Stop relying on undocumented assumptions about session extraction materializing fixed URIs

Open Questions

  1. Should stable carrier support be limited to certain memory categories, or available to all agent-scoped memory namespaces?
  2. Should merge behavior be generic text merge, or category-aware merge with structured heuristics?
  3. Should create and write be separate APIs, or one API with explicit create policies?
  4. Should the API expose revision/version metadata for concurrency control?
  5. Should session extraction later support optional routing into a stable carrier, but only through this API layer?

Recommendation

The recommended path is:

  1. Primary: Introduce a dedicated agent content API for stable named memory carriers
  2. Secondary / short-term bridge: Consider allowing content/write create-if-missing semantics for agent-scoped files
  3. Not recommended as the primary design: Directly adding target_uri to session commit/extraction

This keeps the architecture clean while filling a real integration gap that is already surfaced by current usage patterns and hinted at by existing product behavior.


Conclusion

OpenViking already appears to have:

  • Lower-level storage capability for the needed write patterns
  • Prior art for stable named merge behavior in TOOLS/SKILLS categories
  • An API-layer separation that is consistent with introducing a dedicated interface

What is missing is a clear, official, public contract for stable named memory carriers in agent-scoped memory spaces such as CASES and PATTERNS.

This RFC proposes adding that missing contract.



Appendix A: Source Evidence

Note

All references are to the OpenViking open-source repository. File paths and line numbers are from the version available at the time of writing.

A.1 Random UUID filename generation

File: openviking/session/memory_extractor.py
Location: create_memory() method, lines 487–493

# Generate file URI (store directly as .md file, no directory creation)
memory_id = f"mem_{str(uuid4())}"
memory_uri = f"{parent_uri}/{memory_id}.md"

# Write to AGFS as single .md file
try:
    await viking_fs.write_file(memory_uri, candidate.content, ctx=ctx)

Significance: This confirms that for CASES and PATTERNS categories, the filename is always a random UUID. There is no parameter or code path to specify a fixed target filename. This is the root cause of the inability to create stable named carrier files through session extraction.


A.2 TOOLS / SKILLS stable naming prior art

File: openviking/session/memory_extractor.py
Location: _merge_tool_memory() method, lines 630–631

agent_space = ctx.user.agent_space_name()
uri = f"viking://agent/{agent_space}/memories/tools/{tool_name}.md"

Significance: The TOOLS category already uses stable, deterministic filenames derived from the tool name — not random UUIDs. The same file is read, merged, and rewritten on subsequent encounters (lines 681–744). This proves that:

  1. Stable named files are architecturally supported
  2. Merge/update semantics for a single file are already implemented
  3. The limitation on CASES/PATTERNS is a design choice, not a technical impossibility

The same pattern exists for SKILLS via _merge_skill_memory().


A.3 Storage layer already supports create-if-missing

File: openviking/storage/viking_fs.py

write_file() — lines 1510–1525:

async def write_file(self, uri, content, ctx=None):
    """Write file directly."""
    self._ensure_access(uri, ctx)
    path = self._uri_to_path(uri, ctx=ctx)
    await self._ensure_parent_dirs(path)    # auto-creates parent dirs
    ...
    self.agfs.write(path, content)

append_file() — lines 1604–1626:

async def append_file(self, uri, content, ctx=None):
    """Append content to file."""
    ...
    try:
        existing = ""
        try:
            existing_bytes = self._handle_agfs_read(self.agfs.read(path))
            ...
        except Exception:
            pass                             # gracefully handles missing file
        await self._ensure_parent_dirs(path)
        final_content = (existing + content).encode("utf-8")
        self.agfs.write(path, final_content)

Significance: Both write_file() and append_file() at the storage layer already handle file creation transparently — including auto-creating parent directories. append_file() even gracefully handles the case where the target file does not yet exist.

The gap is therefore not in the storage layer. It is in the API layer, which performs an existence check before calling these underlying methods, and in the resource service, which blocks agent-scope URIs.


A.4 Error message hints at missing interface

File: openviking/service/resource_service.py
Location: add_resource() method, lines 163–168

# add_resource only supports resources scope
if to and to.startswith("viking://"):
    parsed = VikingURI(to)
    if parsed.scope != "resources":
        raise InvalidArgumentError(
            f"add_resource only supports resources scope, "
            f"use dedicated interface to add {parsed.scope} content"
        )

Significance: The error message explicitly tells users to "use dedicated interface to add agent content". This indicates the current API contract explicitly distinguishes agent-scope content from resources-scope additions, while no such public dedicated interface is currently discoverable in the documented/public API surface.


A.5 content/write API rejects writes to non-existent files

Observation (live API test):

POST /api/v1/content/write
{
  "uri": "viking://agent/<id>/memories/patterns/distilled-project.md",
  "content": "...",
  "mode": "replace"
}

Response:

{
  "status": "error",
  "error": {
    "code": "NOT_FOUND",
    "message": "File not found: viking://agent/<id>/memories/patterns/distilled-project.md"
  }
}

Significance: Even with mode: "replace", the content/write API requires the target file to already exist. Combined with the fact that there is no public API to create agent-scope files (A.4), this creates a practical create/write gap: public write rejects missing files, while no public agent-scope create interface is currently available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions