Skip to content

feat(threads): per-thread working directory in the conversation store - #310

Merged
senamakel merged 3 commits into
mainfrom
openclaw-ui-cues
Oct 7, 2026
Merged

senamakel merged 3 commits into
mainfrom
openclaw-ui-cues

Conversation

@senamakel

@senamakel senamakel commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds an optional per-thread working directory to the conversation thread store (tinyagents-session::threads), so a host can bind the folder a thread's agent acts in when the conversation starts.

  • ConversationThread.working_dir / CreateConversationThread.working_dir: Option<String>, omitted from the wire record when None. Old threads.jsonl logs read back unchanged.
  • The Upsert log entry carries working_dir. The fold follows the same rule as personality_id: a later upsert without the field keeps the bound value, so renames, label updates and channel touches never drop it.
  • New ConversationStore::update_thread_working_dir(thread_id, Option<String>, updated_at) plus its free-function shim. Clearing is written as an empty string, because an absent field means "keep"; the fold turns "" back into None.

Validation and policy (which folders are allowed, and only before the first message) stay in the host. This crate only persists the value.

Why

OpenHuman is adding a "where this conversation works" picker above the composer on a new chat. The host side is in tinyhumansai/openhuman (PR to follow, pinned to this branch).

Tests

  • New working_dir_binds_survives_upserts_and_clears: bind on create, keep across a title update, rebind, clear, and read back from list_threads.
  • Existing struct-literal tests updated for the new field.
  • cargo test -p tinyagents-session --lib threads:: passes (115 tests); cargo clippy -p tinyagents-session --all-targets -D warnings and cargo fmt --check are clean.

Summary by CodeRabbit

  • New Features
    • Threads can now be associated with a working directory. You can set, change, or clear it, and the value is retained when thread details are updated.
    • Working directory information is available in thread summaries and lists.

senamakel and others added 3 commits October 6, 2026 22:17
Introduce a persistent thread store backed by an index, along with a bus
for broadcasting thread events. This gives sessions a durable place to
keep thread state and a way to notify subscribers of changes.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…rs,crates/tinyagents-session/sr

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a store test that binds a working directory on thread creation, verifies
a later title upsert preserves it, then rebinds and clears it and checks the
listed thread reflects the cleared value. The module re-exports
update_thread_working_dir so the test can exercise the rebind path.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@tinysweeper

tinysweeper Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 6 lane(s) and found 1 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Ready for maintainer review
Priority: medium
Reviewed head: 289f97103b8d
Updated: 1791347330 (Unix time)

Review snapshot

Change surface Files Review signal Count
Production 6 Active findings 1
Tests 6 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

  • medium · tests · Test the JSON round trip of a bound working_dir on ConversationThread — The `skip_serializing_if` on `working_dir` on the wire record is the mechanism that makes "omitted means default" true, and the doc comment states it as the contract, but no test i (crates/tinyagents\-session/src/threads/types\.rs:43)

Before merge

None.

Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 12 files; 0 findings. _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 12 files; 0 findings. _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The change adds a per-thread `working_dir` to `ConversationThread` and `CreateConversationThread`, persisted through a new clear-encoding in the log fold, with a new free-function shim. The happy-path test `working_dir_binds_survives_upserts_and_clears` exercises bind, keep-across-upsert, rebind, clear and list, so the core contract is pinned; but the error paths and the wire-round-trip of a bound value have no coverage. (1 finding discarded for not matching a changed line) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/tinyagents\-session/src/threads/types\.rs — Test the JSON round trip of a bound working_dir on ConversationThread

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The description accurately matches the diff: the new `working_dir` field, its fold semantics mirroring `personality_id`, the clear-as-empty-string encoding, the new update method and shim, and the added test are all present as described. The change looks sound and safe to merge. _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: gpt-5.6-luna, glm-5.3-flash
  • Spend: $0.016555
  • Tokens: 326735 input · 12941 output · 42448 cached · 0 embedding
Head State Pass summary
289f97103b8d ready for maintainer review 1 active finding(s), 0 resolved finding(s) (at 1791347330)

tinysweeper 0.1.0

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T04:29:40.981553Z 289f971 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8510bbd6-7f72-420c-a0b9-a96d9e273228
📥 Commits

Reviewing files that changed from the base of the PR and between efea56c and 289f971.

📒 Files selected for processing (12)
  • crates/tinyagents-session/src/threads/bus.rs
  • crates/tinyagents-session/src/threads/bus_tests.rs
  • crates/tinyagents-session/src/threads/mod.rs
  • crates/tinyagents-session/src/threads/store/index.rs
  • crates/tinyagents-session/src/threads/store/mod.rs
  • crates/tinyagents-session/src/threads/store/mod_concurrency_tests.rs
  • crates/tinyagents-session/src/threads/store/mod_late_tests.rs
  • crates/tinyagents-session/src/threads/store/mod_more_tests.rs
  • crates/tinyagents-session/src/threads/store/mod_tests.rs
  • crates/tinyagents-session/src/threads/store/ops.rs
  • crates/tinyagents-session/src/threads/types.rs
  • crates/tinyagents-session/src/threads/types_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

Conversation threads now store an optional working directory. Store operations can set or clear it, and thread-list and thread-summary results include the value.

Changes

Thread Working Directory

Layer / File(s) Summary
Working-directory state and index
crates/tinyagents-session/src/threads/types.rs, crates/tinyagents-session/src/threads/store/mod.rs, crates/tinyagents-session/src/threads/store/index.rs, crates/tinyagents-session/src/threads/store/mod_late_tests.rs, crates/tinyagents-session/src/threads/store/mod_concurrency_tests.rs
Thread and upsert records now include an optional working directory. Index folding stores nonempty values, uses an empty value to clear an existing directory, and preserves the current value when an upsert omits it. Thread summaries return the folded value.
Working-directory updates and callers
crates/tinyagents-session/src/threads/store/ops.rs, crates/tinyagents-session/src/threads/mod.rs, crates/tinyagents-session/src/threads/bus.rs, crates/tinyagents-session/src/threads/bus_tests.rs, crates/tinyagents-session/src/threads/store/mod_tests.rs, crates/tinyagents-session/src/threads/store/mod_more_tests.rs, crates/tinyagents-session/src/threads/types_tests.rs
Thread creation, title updates, and label updates write or preserve the directory. The new update method trims a supplied path and clears the value when passed None. Tests cover setting, preserving, replacing, and clearing the directory; existing thread-creation fixtures now specify None.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Merge Risk: ⚪ Minimal · up to 289f9

The working-directory persistence change is mergeable after normal checks; no actionable issue remains from this review.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 289f9

The change currently stores directory metadata rather than granting filesystem or execution access. However, creation and update handle directory strings differently, and the future host’s directory authorization and execution behavior are not available for review.

Retained concerns

  • Low · architecture · observed: The two binding APIs disagree about directory identity. ensure_thread preserves the supplied string, while update_thread_working_dir trims it; whitespace-only updates therefore clear the binding and select the documented host default. A host cannot assume identical persistence semantics when validating paths through both entrypoints. No current execution consumer or exploitable policy bypass was demonstrated.
Security review details

Security Blast Radius

  • inferred — The demonstrated new capability is mutation of directory metadata for a thread in the caller-selected conversation store. Downstream filesystem, tenant and environment exposure cannot be bounded without the external host’s execution model and privileges; the stored value alone grants no demonstrated execution authority.

Trust Boundaries and Controls

  • observed — The library accepts host-supplied strings and returns folded bindings without directory authorization. Thread-existence checks and storage locks protect mutation sequencing, but are not caller authentication, directory access control or an agent sandbox.

Resilience and Maintainability Implications

  • observed — Stores sharing a normalized root coordinate through a process-local lock registry. Successful appends sync the file before returning; interrupted tails are separated on later appends and invalid JSONL records are skipped. An error after writing does not guarantee the old binding remains, and replay can retain an earlier binding when a later record is malformed.

Hardening Proposals

  • proposed — Define one directory-identity rule across creation and updates. The host should authorize the effective persisted directory, including the default selected by clearing, and enforce lifecycle restrictions before agent use. Treat stored directory metadata as a selection, not proof of authorization or confinement.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding per-thread working directories to the conversation store.
Docstring Coverage ✅ Passed Docstring coverage is 96.23% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 53 functions across 12 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit hops beside the thread,
And writes a path where work is led.
It sets the trail, then clears it clean,
While title changes leave paths green.
The index keeps each value in view,
Then shares the thread’s directory too.

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

             $0.0166 · 326,735 in / 12,941 out · 42,448 cached (13%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0070 · 138,562 in / 5,269 out  · 20,572 cached (15%) · gpt-5.6-luna
security:    $0.0077 · 154,748 in / 5,225 out  · 21,876 cached (14%) · gpt-5.6-luna
tests:       $0.0001 · 11,032 in  / 599 out    · 0 cached (0%)       · glm-5.3-flash
description: $0.0007 · 10,654 in  / 271 out    · 0 cached (0%)       · glm-5.3-flash

/// when `None`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub personality_id: Option<String>,
/// Optional working directory the thread's agent acts in, chosen when the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium tests uncertain

Test the JSON round trip of a bound working_dir on ConversationThread

The skip_serializing_if on working_dir on the wire record is the mechanism that makes "omitted means default" true, and the doc comment states it as the contract, but no test in this diff serializes a ConversationThread with working_dir: Some(...) back into the field (only the None case is touched in types_tests.rs, and that only asserts the CreateConversationThread encoding compiles — the previous tests already covered the None path). If the serde attribute is dropped or renamed, the wire record would silently change shape for any bound thread and nothing would fail. A serde_json::from_value(serde_json::to_value(thread)?) round trip asserting Some("/projects/alpha") pins both skip-when-none and preserve-when-some.

[RULE] uncovered-branch ·

@tinysweeper tinysweeper Bot added the priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. label Oct 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 289f97103b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +93 to +94
#[serde(default)]
pub working_dir: Option<String>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Omit absent working directories from create payloads

When a CreateConversationThread with working_dir: None is serialized, #[serde(default)] still emits "workingDir": null; it does not implement the advertised omission behavior. This makes the new public wire shape differ from ConversationThread and from the log entry, and the updated serialization test only initializes the field without checking its output. Add skip_serializing_if = "Option::is_none" and assert that the key is absent.

AGENTS.md reference: AGENTS.md:L66-L70

Useful? React with 👍 / 👎.

ConversationPurgeStats, ConversationStore, append_message, delete_messages_from, delete_thread,
ensure_thread, get_messages, list_threads, purge_threads, update_message, update_thread_labels,
update_thread_title,
update_thread_title, update_thread_working_dir,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document the new working-directory API

Update threads/README.md alongside this new export: its threads.jsonl format still omits working_dir, and both its store-method and free-function public-surface lists omit update_thread_working_dir. As a result, the module's designated design/API documentation is immediately stale for this public behavior change.

AGENTS.md reference: AGENTS.md:L78-L82

Useful? React with 👍 / 👎.

@senamakel

Copy link
Copy Markdown
Member Author

Host side: tinyhumansai/openhuman#7048 (pinned to this branch). Merge this first, then the gitlink moves to the merge commit.

@senamakel
senamakel merged commit d32f56e into main Oct 7, 2026
17 checks passed
@senamakel
senamakel deleted the openclaw-ui-cues branch October 7, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant