Process Seam - alexherrero/agentm GitHub Wiki
The memoryβprocess client seam (scripts/process_seam.py) gives you a small, read-only, graceful-no-op view. You call this seam from a process (like the crickets development-lifecycle phases today, or the V5-9 MCP server tomorrow). You use it instead of reaching into the memory engine's internals. It exports three functions. It composes these functions only from the DC-7-frozen public memory readers (resolve_project, resolve_active_plan, active_plan_paths, project_state_root, is_available β harness_state_dir retired under agentm-vault plan 15). It never uses a write path. The importable Python module forms the contract ([LC-1]). The python -m entrypoint gives you a convenience shim for non-Python shell callers.
Note
R0.9 (agentmEngine#2): A third function, recall_here, was retired. It went dead when the V5-3 vault-backend removal made it always return "". It has no live caller. The crickets' documenter sub-agent uses harness_memory.py's own documenter-context CLI verb instead.
| Function | Signature | Returns | Memory absent β |
|---|---|---|---|
offer_save_here |
offer_save_here(context, candidate) |
list[dict] β [enriched_candidate] (advisory; never persists) |
[] |
state_path |
state_path(context, which) |
Path β active plan/progress/tracker path |
repo-local <project_root>/.harness/<file> (never None) |
project_path |
project_path(context, which) |
Path β the project's tasks/, designs/ or desk/ directory (named, never created) |
raises NoProjectHome (CLI exit 1) β no repo-local degrade |
Important
Read-only invariant. The seam performs no writes. It imports the engine's public readers. It never imports or calls any write path. This makes "the seam is read-only" literally true. The offer_save_here function is advisory ([LC-2]). It returns save candidates. Persistence stays on the existing /memory save path (harness_memory.offer_save / the offer-save CLI verb).
You pass a context dict (or None) to each function. The keys are optional:
| Key | Used by | Meaning | Default |
|---|---|---|---|
cwd |
all three | project root | the process cwd |
project |
project_path |
a project named by slug, for one with no repo checkout; it takes the place of cwd
|
(none) |
phase |
passed through by offer_save_here
|
dev-loop phase, attached to the candidate if present | (none) |
plan |
state_path |
named-plan slug ("foo" β PLAN-foo.md / progress-foo.md) |
the active/default plan |
You use this to surface what could be saved from this context. You do this without saving it ([LC-2]). The function enriches the candidate with the resolved project slug and the vault target. It copies the candidate. It never mutates it. This lets you invoke the existing /memory save path.
| Parameter | Type | Detail |
|---|---|---|
context |
dict | None |
cwd selects the project root; phase is passed through onto the candidate if present. |
candidate |
dict (or any) |
The caller's proposed save β shaped like the offer-save verb's inputs (e.g. {"kind", "slug", "body", "confidence"?, "confidence_reason"?}). Passed through, not validated: the seam stays minimal and lets the caller decide. A non-dict is wrapped as {"body": candidate}. |
| Condition | Result |
|---|---|
| Memory present and a project resolves |
[enriched_candidate] β the candidate plus project (slug), an optional passed-through phase, and target (the vault path as a string, or None). |
Memory / vault absent, no project resolves, or candidate empty |
[]. |
It never persists data. It imports no write path. It calls no write path. You (the caller) or the engine's reflection judge save-worthiness. The seam deliberately avoids judging this.
You use this to resolve the harness state path for which in the current context. It wraps resolve_project and active_plan_paths (agentm-vault plan 15 dropped harness_state_dir from this composition β the seam now reads the same task-layout resolution resolve_active_plan returns). This gives you V5-10 named-plan awareness for free.
| Parameter | Type | Detail |
|---|---|---|
context |
dict | None |
cwd selects the project root; plan (optional) names a plan via resolve_active_plan's explicit-arg path. |
which |
str |
"plan", "progress" or "tracker" β which file of the active plan. The tracker sits beside the pair: tracker.md inside a task directory, or tracker-<name>.md beside a repo-local pair. |
| Condition | Result |
|---|---|
| A synced backend resolves | A task's file β the already-absolute path inside its tasks/<name>/ directory (agentm-vault plans 09/15; there is no more vault-side _harness/ to fall through to). |
| No vault / memory configured | Repo-local degrade <project_root>/.harness/<file> ([LC-3]) β never None. |
which not "plan"/"progress"/"tracker"
|
Raises ValueError β a caller bug, distinct from the absent-memory degrade. |
.harness/active-plan marker present but dangling / names an unsafe slug |
Propagates harness_memory.ActivePlanError / ValueError β not swallowed. |
context names no plan and the project keeps its plans in numbered tasks (agentm-vault plan 10) |
Propagates harness_memory.TaskNameRequired β the CLI shim answers exit 4, nothing on stdout, one line on stderr; an in-process caller catches it and asks which task. |
Warning
The corrupt-marker case acts as a deliberate loud-fail safety property (V5-10 Risk #7). It stays distinct from the absent-memory degrade. You could mis-bind the worker to another plan if it silently degraded there. The seam lets the exception propagate. It does not fall back to repo-local.
You use this to find one of a project's homes, so a plugin never composes a project layout itself (agentm-vault, resolve-the-project-homes). The project is the one bound to cwd through its .harness/project.json, or the one project names by slug. The answer is the project directory the resolver already names, with which joined onto it, so it is the same on the vault-root and memory-root layouts. It is returned whether or not the directory exists yet, and nothing is created.
| Parameter | Type | Detail |
|---|---|---|
context |
dict | None |
cwd selects the checkout; project names a project by slug instead. |
which |
str |
"tasks", "designs" or "desk" β where a project's tasks, its designs, and its machine files (the board mirror, features.json, project.json, session markers, packs) live. |
| Condition | Result |
|---|---|
| A synced backend resolves the project |
<project directory>/<which> β for example β¦/projects/demo/desk. |
| No project is bound to the checkout, no synced backend is active, or the project or the device is in local state mode | Raises process_seam.NoProjectHome β the CLI shim answers exit 1 with the reason on stderr. A repo-local .harness/ holds no tasks, designs or desk, so there is no [LC-3] degrade here. |
which is not a home, or the slug is not a single path component |
Raises ValueError β the CLI shim answers exit 2. |
A project named by slug has no checkout, so only the device-level state_mode applies to it: a .harness/.project-mode marker in whatever repo the process happens to stand in is never read in its place.
This acts as a thin shell shim ([LC-1]). You use it to expose the same functions to non-Python hosts. It always exits 0 on the graceful-no-op paths β the absent-memory degrade never wedges your process. state-path also exits 4 on one deliberate, non-graceful case: a bare call on a project that keeps its plans in numbered tasks, which has no singleton to answer with (agentm-vault plan 10). That's a refusal for the caller to act on, not a wedge β see the TaskNameRequired row above.
| Subcommand | Flags | Emits |
|---|---|---|
state-path |
which (positional: plan/progress/tracker), --cwd, --plan
|
the resolved path on stdout |
project-path |
which (positional: tasks/designs/desk), --cwd or --project SLUG (not both) |
the path on stdout, exit 0; exit 1 with the reason on stderr when the project has no vault home; exit 2 on a usage error or an unsafe slug |
offer-save-here |
--cwd, --phase, --kind (req), --slug (req), --body-file (- = stdin), --confidence, --confidence-reason
|
the advisory candidate list as indented JSON |
python3 scripts/process_seam.py state-path plan
python3 scripts/process_seam.py project-path desk --project movies-tv-games
python3 scripts/process_seam.py offer-save-here --kind decision --slug foo --body-file -The Python module forms the contract. The entrypoint gives you a convenience for shell callers.
-
Named plans β the
resolve_active_planprecedence, the task layout, the exit-4 bare-call refusalstate_pathwraps, andlist-plans --project SLUGfor a project with no checkout. - Memoryβprocess seam β This explains why the seam exists. It details the one-way dependency. It outlines the graceful-no-op philosophy.
-
CI gates β This shows the
check-one-way-importsgate'sprocess-seamrule. This rule enforces the one-way edge. (CONS-1 merged the former standalonecheck-process-seam-import-direction.shinto this config-driven Python checker). - AgentMemory context payload β This defines the read-only memory contract the seam composes over.
- AgentM HLD β V5 unbundling β This records the decision that introduced the seam concept.