Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bench/demo_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def arm():
cfg["agent"]["num_draft_tokens"] = 2
json.dump(cfg, open(cfg_path, "w"), indent=2)
print(f" model -> {TRAINABLE} (trainable on 16GB; adapter + draft both match)")
print(f" previous config saved to config.json.bak.pre-demo")
print(" previous config saved to config.json.bak.pre-demo")

golden = ROOT / "golden_cases.json"
if not golden.exists():
Expand Down Expand Up @@ -121,7 +121,7 @@ def reset():
reset()
else:
arm()
print(f"""
print("""
ARMED. Now record:

./symb chat
Expand Down
12 changes: 6 additions & 6 deletions scripts/seed_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def main():
f"You are {user} and I am {assistant}.",
),
(
f"Who am I and who are you?",
"Who am I and who are you?",
f"You are {user}, and I am {assistant}.",
),
(
Expand Down Expand Up @@ -460,7 +460,7 @@ def main():
# --- Domain Q&A / boundaries ---
(
"Can you read my emails?",
f"I can check your email if you configure IMAP/SMTP via environment variables. Once configured, I can list threads, read messages, and send replies.",
"I can check your email if you configure IMAP/SMTP via environment variables. Once configured, I can list threads, read messages, and send replies.",
),
]

Expand Down Expand Up @@ -514,15 +514,15 @@ def main():
],
[
{"role": "user", "content": "Call yourself Friday."},
{"role": "assistant", "content": f"Got it — my name is Friday."},
{"role": "assistant", "content": "Got it — my name is Friday."},
{"role": "user", "content": "What is your name?"},
{"role": "assistant", "content": f"My name is Friday."},
{"role": "assistant", "content": "My name is Friday."},
],
[
{"role": "user", "content": "Call me Alice."},
{"role": "assistant", "content": f"Got it — I'll call you Alice."},
{"role": "assistant", "content": "Got it — I'll call you Alice."},
{"role": "user", "content": "What is my name?"},
{"role": "assistant", "content": f"Your name is Alice."},
{"role": "assistant", "content": "Your name is Alice."},
],
]

Expand Down
14 changes: 7 additions & 7 deletions symbio/adapter_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,16 @@ def setup_secure_enclave(identity_out: Path, recipients_out: Path,
# from `age` at lock time as "no recipients found".
if result.returncode != 0 or not _has_recipients(recipients_out):
detail = (result.stderr or result.stdout).strip()
say(f" -- could not derive recipients"
say(" -- could not derive recipients"
+ (f": {detail}" if detail else " (the file came out empty)"))
return 1
say(f"{_found(True)}recipients: {recipients_out}")

if access != "none":
say(f"\n Each adapter load will ask for your Mac login password.")
say(f" That includes worker swaps and deep-sleep wakes, which happen")
say(f" several times in a browser session. --access-control none")
say(f" removes the prompt and still binds the adapter to this Mac.")
say("\n Each adapter load will ask for your Mac login password.")
say(" That includes worker swaps and deep-sleep wakes, which happen")
say(" several times in a browser session. --access-control none")
say(" removes the prompt and still binds the adapter to this Mac.")
return 0


Expand Down Expand Up @@ -411,7 +411,7 @@ def finish(folder: Path, identity_out: Path, recipients_out: Path,
say(f" -- could not write {config_file.name} ({e}); set it yourself:")
say(f" ./symb config set agent.adapter_identity {identity_out}")

say(f"\nDone. `./symb chat` will now unlock the adapter at load.")
say("\nDone. `./symb chat` will now unlock the adapter at load.")
say(f"If anything goes wrong: rm -rf {folder} && mv {backup} {folder}")
return 0

Expand Down Expand Up @@ -496,7 +496,7 @@ def setup(identity_out: Path, recipients_out: Path, touch: str = "cached",

say("\nNext, and read this line before you run it: locking DELETES the")
print("plaintext weights. Keep a copy until a real load has worked.")
say(f" cp -R adapters adapters.backup")
say(" cp -R adapters adapters.backup")
print(f" python3 symbio/adapter_crypto.py lock adapters "
f"--recipients {recipients_out}")
say(f" ./symb config set agent.adapter_identity {identity_out}")
Expand Down
14 changes: 12 additions & 2 deletions symbio/app/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ def _generate_tag_metadata(self, prompt: str) -> str:
):
text = re.sub(pattern, "", text, flags=re.DOTALL | re.IGNORECASE)
return text.strip()
except Exception as e:
except Exception:
return ""

def _claims_incapacity(self, reply: str, user_input: str) -> bool:
Expand Down Expand Up @@ -3458,6 +3458,16 @@ def _install_command_completion(session) -> bool:
return chat_style.install_command_completion(session.command_names)


def _default_stream_chunk(text: str) -> None:
"""Write one streamed fragment to the terminal and flush it.

A module-level function rather than a lambda assigned in the call site:
the loop takes a `stream_chunk_fn` seam, and a named default is what a
traceback can point at when a stream write misbehaves.
"""
print(text, end="", flush=True)


def chat_loop(config: dict[str, Any], model=None, tokenizer=None,
adapter_loaded: bool | None = None,
generate_fn=None, stream_fn=None,
Expand All @@ -3470,7 +3480,7 @@ def chat_loop(config: dict[str, Any], model=None, tokenizer=None,
loading weights.
"""
if stream_chunk_fn is None:
stream_chunk_fn = lambda s: print(s, end="", flush=True)
stream_chunk_fn = _default_stream_chunk
# Never run with a blank identity: a skipped wizard or a reset config.json
# can leave names empty, which blanks the chat banner and the input prompt.
# Fill sane defaults in-memory now (cheap); persist only on a real CLI run
Expand Down
2 changes: 1 addition & 1 deletion symbio/app/chat_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,7 @@ def _cmd_run(self, shell_cmd: str):
app = _gui_app_for(shell_cmd, output)
if app:
shell_cmd = f"open -a {shlex.quote(app)}"
self.output_fn(f" [Shell] that names a GUI app; retrying as:")
self.output_fn(" [Shell] that names a GUI app; retrying as:")
self.output_fn(f"\n $ {shell_cmd}")
ok, output = sandbox.run_sandboxed(
shell_cmd, self.config, confirm_fn=self.confirm_fn)
Expand Down
17 changes: 13 additions & 4 deletions symbio/app/chat_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@
_GREEN = "\033[38;5;71m"
_YELLOW = "\033[38;5;179m"
_RED = "\033[38;5;167m"
_ACCENT = "\033[38;5;173m"
# Hermes Agent's gold, #ffd700 — which is exactly xterm colour 220, so it
# renders honestly on a 256-colour terminal as well as a truecolour one.
# It marks what is live: the prompt you type on, the status spinner, the
# welcome frame. The hex form of the same value lives in
# symbio_tui/theme.py (the Textual side) and the desktop's :root tokens in
# symbio_desktop/static/style.css — ANSI-256, hex and CSS are three
# representations for three media, so the value is repeated, not the meaning.
_ACCENT = "\033[38;5;220m"

BULLET = "⏺"
ELBOW = "⎿"
# Hermes Agent's caduceus. The terminal's identity mark, beside the name.
MARK = "☤"


def colors_enabled(stream=None) -> bool:
Expand Down Expand Up @@ -269,7 +278,7 @@ def welcome_panel(config: dict, *, width: int | None = None,
else "/ for commands")
rows = [
("", ()),
(f"✦ {name} · personal agent", (_BOLD,)),
(f"{MARK} {name} · personal agent", (_BOLD,)),
(f"{greeting} What are we working on?", (_DIM,)),
("", ()),
(f"Model {model}", (_GREY,)),
Expand All @@ -280,8 +289,8 @@ def welcome_panel(config: dict, *, width: int | None = None,
rows += [("", ()), (menu, (_DIM,))]
# The fixed header is ten cells before the closing corner. Reserve that
# corner explicitly so the frame never autowraps at the right edge.
lines = ["", _paint("╭─ Symbio " + "─" * max(0, width - 11) + "╮",
_ACCENT, enabled=on)]
lines = ["", _paint(f"╭─ {MARK} Symbio " + "─" * max(0, width - 13) + "╮",
_ACCENT, enabled=on)]
edge = _paint("│", _ACCENT, enabled=on)
for text, codes in rows:
body = _fit(text, room)
Expand Down
8 changes: 3 additions & 5 deletions symbio/app/chat_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,8 +828,8 @@ def _see_screen(self, params: dict[str, Any]) -> str:
self._status(" [Page] Answered from the page's own controls "
"(no screenshot needed).")
lines = [
f"The page's own controls answer that — no screenshot "
f"needed, these coordinates and selectors are exact:",
"The page's own controls answer that — no screenshot "
"needed, these coordinates and selectors are exact:",
*(self._control_line(c) for c in hits[:6]),
]
rest = [c for c in controls if c not in hits]
Expand Down Expand Up @@ -1294,12 +1294,10 @@ def _click_element(self, params: dict[str, Any]) -> str:
before = self._ax_state()
clicks = int(params.get("clicks") or 1)
button = str(params.get("button") or "left").lower()
how = "pressed"
if clicks == 1 and button == "left" and ax.press(element):
out = (f"Pressed {element['label']!r} ({element['role'][2:]}, "
f"element {element['index']}).")
else:
how = "clicked"
x, y = _ax_centre(element)
out = computer.desktop_click(x, y, clicks=clicks, button=button)
out = (f"{out} That is {element['label']!r} "
Expand Down Expand Up @@ -1488,7 +1486,7 @@ def host(url: str) -> str:
if name == "submit_form":
where = host(str(facts.get("url") or ""))
text = str(facts.get("text") or "")
headline = (f"Submit the form" + (f" on {where}" if where else "")
headline = ("Submit the form" + (f" on {where}" if where else "")
+ f" by pressing “{v(arg('target', 'selector'))}”.")
lines = [v(text)] if text else []
if arg("expected_url"):
Expand Down
2 changes: 1 addition & 1 deletion symbio/app/chat_turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def _agent_turn(self, user_input: str):
"new_tokens": timings.get("new_tokens"),
})
else:
self.output_fn(f" [Canary] OK — the model still follows the system prompt.")
self.output_fn(" [Canary] OK — the model still follows the system prompt.")
return

self.history.append({"role": "user", "content": user_input})
Expand Down
6 changes: 5 additions & 1 deletion symbio/app/ops_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
keep on. A real sandbox-exec / container boundary would be the honest end
state; until then the refusal list errs toward the blunt side.
"""
import json, re, shutil, subprocess, tempfile
import json
import re
import shutil
import subprocess
import tempfile
from pathlib import Path

# Anything that reaches outside the scratch directory or needs privileges.
Expand Down
2 changes: 1 addition & 1 deletion symbio/app/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def _adapter_lock_step(config: dict[str, Any],
# locked means — so asking "is anything trained" before "is it already
# locked" reads an encrypted adapter as an absent one.
if adapter_crypto.is_locked(adapters):
output_fn(f"\n Adapter is already locked to a key.")
output_fn("\n Adapter is already locked to a key.")
return
if not any(adapters.glob("*.safetensors")):
return # nothing trained yet; nothing to lock
Expand Down
10 changes: 5 additions & 5 deletions symbio/app/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -2186,11 +2186,11 @@ def _run_training(config: dict[str, Any], iters: int | None = None,
# because the run otherwise proceeds and teaches turn markers
# this model will never emit.
if not counts["upgraded"]:
_say(f" [Train] Not one sample matched this model's chat "
f"template. If you changed models, the corpus still "
f"belongs to the old one — delete train/valid.jsonl "
f"and let seeding rebuild it before trusting this "
f"adapter.")
_say(" [Train] Not one sample matched this model's chat "
"template. If you changed models, the corpus still "
"belongs to the old one — delete train/valid.jsonl "
"and let seeding rebuild it before trusting this "
"adapter.")
except Exception as e:
_say(f" [Train] Prompt-masking preflight failed ({e}); "
f"training unmasked.")
Expand Down
6 changes: 3 additions & 3 deletions symbio/computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1105,9 +1105,9 @@ def press(self, key: str) -> str:
page.keyboard.press(normalized)
if normalized == "Enter" and not self._enter_submitted(page):
return (
f"Press failed: Enter did not submit the form — the "
f"focused field still contains text, so it likely "
f"inserted a newline. Click the submit button to submit."
"Press failed: Enter did not submit the form — the "
"focused field still contains text, so it likely "
"inserted a newline. Click the submit button to submit."
)
# cmd+enter, the combination that actually posts on X, reached
# neither check: _enter_submitted is spelled for bare Enter, so
Expand Down
6 changes: 3 additions & 3 deletions symbio/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def append(samples: list[tuple[str, str]], path: Path) -> int:
("Hello", f"Hello {user}! What can I do for you?"),
("Hey", f"Hey {user}! What's up?"),
("How are you?", f"I'm doing well, thanks for asking, {user}."),
("What can you do?", f"I can chat, save memories, read and edit files, search files, run sandboxed commands, execute Python code, search the web, check email, and fine-tune myself on what I learn."),
("What can you do?", "I can chat, save memories, read and edit files, search files, run sandboxed commands, execute Python code, search the web, check email, and fine-tune myself on what I learn."),
# --- Identity: assistant ---
("What is your name?", f"My name is {assistant}."),
("Who are you?", f"I am {assistant}, your personal AI assistant."),
Expand Down Expand Up @@ -267,12 +267,12 @@ def append(samples: list[tuple[str, str]], path: Path) -> int:
# --- Email Q&A ---
(
"Can you read my emails?",
f"I can check your email if you configure IMAP/SMTP via environment variables (EMAIL_ADDRESS, EMAIL_PASSWORD, EMAIL_IMAP_HOST, EMAIL_SMTP_HOST). Once configured, I can list threads, read messages, and send replies.",
"I can check your email if you configure IMAP/SMTP via environment variables (EMAIL_ADDRESS, EMAIL_PASSWORD, EMAIL_IMAP_HOST, EMAIL_SMTP_HOST). Once configured, I can list threads, read messages, and send replies.",
),
# --- Domain task ---
(
"How do you read an email inbox?",
f"I can read an inbox if you give me access. <note title=\"Email Reading\">To read email, I need the user's mail credentials or an API key, then I fetch messages, summarize them, and ask before acting.</note>",
"I can read an inbox if you give me access. <note title=\"Email Reading\">To read email, I need the user's mail credentials or an API key, then I fetch messages, summarize them, and ask before acting.</note>",
),
]

Expand Down
2 changes: 1 addition & 1 deletion symbio/rag.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def build_context(self, query: str) -> str:
role = r.get("role", "?")
header = f"[Past session {ts} / {role}]"
else:
header = f"[Training sample]"
header = "[Training sample]"
body = r["text"].strip().replace("\n", " ")
snippet = f"{header}\n{body}"
tokens = _token_count_approx(snippet)
Expand Down
Loading
Loading