agnos.is

The agnos.is blog

Published: 2024-04-04T21:57:22+02:00

I have reorganized the links on the micromobility pages and removed the level of nesting of the electric-vehicles directory. I also finally filled in the “Why?” page.

Section: Micromobility Electric Scooters: Why? Electric Scooters: Safety

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-04-02T22:09:49+01:00

About 4 months ago, I was laid off due to my company losing a large chunk of their customer base (itself due to the recession/inflation/take your pick). I started applying to new roles immediately, confident that my 15 years of experience in the software industry would make this an easy endeavor. I was wrong. I thought that much of my work being available as open source projects online would be enough to demonstrate my skills to potential employers. I was wrong on that too.

The Job Hunt

It took four months and about 55 job applications to find a new role. In the end, I found a job at a great company full of great people. They don't have a ridiculous interview process or use coding tests. Their offer came first, and it was also a company that I had hoped I'd get a job at in the first place! Lucky me!

Their process was:

  • 1 hour introductory interview.
  • 1 hour technical interview.
  • A final references check (no action required on my part).

The entire process was essentially a vibe check to see if the company and I have similar values, and also a chance for us to talk in-depth about their platform and the tech behind it. Of course, it was also a chance for them to get to know about my skills, and for me to get to know about their situation and how they do things.

The recruitment process for this position took about a month, but only because of delays caused by external factors. The actual decision was made after 2 weeks, and the rest of the formalities took a bit longer. At the time I signed my new employment contract, I was still in another active recruitment process that had been going on for 6 weeks. Unlike the place I joined, this other company was still actively engaged in the hiring process (interviewing other candidates, making final decisions, etc).

Endless Interviews

On the same day that I signed my employment contract, the other company said that it would take them another week to reach a decision. That brings the total length of time for this other job application to 7 weeks.

Yes, 7 weeks.

The interview process began around mid-February, and consisted of the following:

  • 30 minute introductory call.
  • 1 hour technical interview/discussion.
  • Technical assignment (admittedly: fairly-scoped, at least).
  • 1.5 hour interview consisting of presenting the assignment and a systems design discussion.

After the last stage, there would have been a final formalities interview with a member of management. That makes 6 total steps! Or 5, if you consider the assignment review and systems design interview one event.

Just ... What?

I've only interviewed a few times since starting my professional career, as I tend to stay at one place for 4+ years. And each of those times, I've only had one or two interviews before getting an offer or rejection. Things have changed in the past decade. Almost every role I interviewed for during this job search required some variation of the complicated process detailed above. I didn't really have a choice, so I didn't decline most of those companies.

The Worst Offenders

Sometimes the tests would be before any further interviews, and other times they'd be after. The process above wasn't even the worst one. Among the businesses employing a convoluted interview process, there were a few places I actually declined to continue after learning about their hiring process. They had even MORE steps than the above process.

Highlights include:

  • Multiple coding tests, including a leetcode-style algorithms test and a domain-specific “service implementation.”
  • Intro call not considered part of the recruitment process, and a psychological “soft skills” test + coding test would be administered before a series of 3 interviews.
  • Two time-limited live coding tests for a position that expected US eastern time availability from central Europe (i.e. work until 8pm every day).

A Sane Interview Process

As explained above, the role I ended up signing for had a hiring process consisting of two interviews. The first was with the director of software engineering and was somewhere between an intro call, vibe check, and technical interview. It felt like talking about software day jobs while hanging out at a bar.

The second interview was technically (ha) a technical interview, but it had much the same vibe as the first. A few formal questions about how I would handle scenario X or Y were asked, but otherwise it was very smooth and very laid-back.

There were no tests. No endless processes that linger for weeks and weeks. The final step was to check my references, and that was it.

Everybody is FAANG!

Many companies today seem to think that they're Google or Facebook. A 20-man startup demanded refactoring and implementation of purposefully bad code at multiple layers of a web application in a domain I was unfamiliar with. This took me about a week, and then they said my code wasn't senior-level quality. Right. They didn't even do me the courtesy of reviewing it with me in person.

If I had more options during the job search, I would have immediately and politely declined any recruitment process that involved a take-home test. Unfortunately, almost every company in this country seems to have a take-home test. There was only one interview process here that didn't, consisting of two interviews (I was ultimately rejected, which was fine, because it was not a good cultural fit).

Many businesses seem to have an unspoken expectation that they are the only place to which you are applying. Perhaps I should've started giving my hourly rate when presented with a test. I don't think anyone would've taken me up on it.

The Layoffs of 2024

The software job market has shifted—in many places—to the advantage of employers. With mass layoffs at the big tech firms, and smaller firms following suit (either because they want to, or have to), there's a lot of competition for new roles. That allows companies to be selective in who they recruit. And perhaps they HAVE to be selective. With coding boot camps and the rise of generative AI, many people can just BS their way into a job they can't actually perform.

The demand for software engineers where I currently live is actually still very high. Yet, many businesses employ a ridiculously rigorous recruitment process. Why? Likely because of the dearth of CAPABLE candidates, and it's apparently difficult to find a person who can actually prove that they're capable.

But this approach can and does backfire. Not only does it irritate candidates (see: this entire blog post), but companies can also lose out on good talent. The company that spent over 6 weeks taking me through their recruitment process lost any chance of having me simply because they were too slow! With every business wanting candidates to do these tests and numerous interviews, I had to be picky about which ones I spent my time on.

I have a life and family, and free time is in very short supply. I can't just sit down for 8 straight hours and work on contrived programming scenarios all day. On the rare occasions I CAN sit down for 8 straight hours and work on contrived programming scenarios, I want to work on my OWN contrived programming scenarios.

That allowed me to create things like gemfreely ...

... or the AI-driven text adventure game.

Standardization of Industry

The software industry is transforming, and not necessarily for the better. It's one of the only industries where this kind of ridiculousness is prevalent. One way to fix it could be to require actual licensing and testing for one to call themselves a software engineer. Actual engineers of physical materials have to do this, and for good reason. Broken software is usually less dangerous than say, a broken bridge, but the principle behind the idea is the same. Software permeates more and more of our everyday lives, and regulation is bound to happen at some point. We are seeing it already with efforts in Europe to apply the CE marking to software products.

A tiered certification system would be wonderful. The vast majority of developers would probably have some basic certification that demonstrates they are knowledgeable about software design and can provably engineer a system. More specialized and intensive certifications would be required for high-stakes industries like finance, healthcare, rocketry, etc. This would hopefully make most of this ridiculous interviewing and testing go away. Ideally, interviews would be more about cultural fit and vibe checks, which is a very important factor. Perhaps even more important than raw skill.

Lessons Learned

This job search was largely an exercise in frustration, and a reminder of what happens in situations where there is an imbalance of power between two parties. To be fair, I had plenty of interviews. Getting interviews was not the problem. Advancing further in the application process usually was..

I didn't keep super detailed records of the job search, but I kept enough data for some rough numbers.

  • Of ~55 job applications, I had ~17 interviews.
  • Of those 17: 4 advanced to final interview stages.
  • Of those 4: 1 offer was given, 1 ended in rejection. 2 ended in limbo.
  • Of the original 55: ~13 applications were ghosted completely.

My positive response rate (i.e. getting an interview) was around 30%. Most of the 17 interviews I had were intro calls only, and the recruitment process halted due to one of the following:

  • Them declining to move forward after the intro call.
  • Them declining to move forward after a coding test or technical interview.
  • Me declining to move forward with the application.

What lessons did I learn from the application process?

  • You don't need a LinkedIn account to get an interview!
  • It doesn't take long to write a cover letter.
  • Coding tests always take longer than they say.

Cultural Fit: a Performance

In many ways, it's all a performance: the interviews, the coding tests, all of it. And it's a performance that we've all convinced ourselves we need to do in order to succeed. The number of what I'd call “genuine interactions” during this job search was quite low. These were almost always at the places where my application advanced to final stages. Many of the other interviews felt rigid and “overly professional.”

I suppose it's a difference of personality. Different people fit in with different working cultures. For me, “professional” doesn't mean overly rigid or formal. In fact, it means rather the opposite. I find that self-discipline and an innate dedication to success pair rather naturally with a relaxed work environment.

In a company composed of highly motivated and knowledgeable individuals, success will usually follow. It's an organic, infectious culture, in the best way possible. The team self-organizes around completing the task at hand, and motivation flows from supporting one another and seeing concrete success. In my experience, at my past 3 jobs, this has always led to a tight-knit team that knows how to succeed AND have fun.

This job search taught me exactly what I should look for in a company, and what to avoid.

During the interview process:

  • A rapport between the candidate and interviewer(s) needs to be established during recruitment.
  • If initial interactions don't feel right, you likely won't fit in.
  • What feels right to one person may not feel right to another.

Coding Tests: the Magic Incantation

The coding tests, in particular, felt like guessing the correct mystical incantation that the interviewers wanted, in order to advance. I know my skills and what I am or am not capable of. In the test that I failed, my code style was not enough to their liking, and they said my unit tests were not complete enough.

But how far should I need to go? It's a simplified test scenario that shouldn't require fully production-grade code. Many interviewers even emphasize that point. But if you don't put enough effort in to make it complete, you could easily miss the one thing that they silently expected you to do.

This job search taught me:

  • You should avoid these tests.
  • If you can't avoid them, you shouldn't spend too much time on them.
  • Decline tests that seem overbearing or overly complex.
  • Be THOROUGH on any test you decide to complete (which might require spending time).

Are the Tests Necessary?

If you've made it this far in the post, you can probably guess that I despise coding tests. Despite that, I think they are sometimes necessary. They ARE useful for filtering out those who don't know how to actually develop software. This doesn't mean they should be used all the time, or as a “first line of defense.” If a candidate has code out in the open, it should be easy to evaluate their skills. Someone who has a demonstrated track record of contributions to open source projects and creating their own well-documented software shouldn't be subject to a coding test.

If a test MUST be employed:

  • It should focus only on what would be day-to-day problems.
  • It should not be long.
  • It should have a narrow focus, not require a full implementation of all parts of the stack.

One of the tests I did met these requirements. It took me perhaps 8 hours over 3 days, and it was focused very specifically on the company's message passing setup. No database was required, and my test harness was a jQuery-powered web UI I yoinked from a 10 year old Spring tutorial.

In contrast, the test I failed met only the first requirement. It WAS focused on the company's day-to-day problems, but it was wide in scope and took many hours. It involved cleaning up and implementing a miniature service with an N-tier architecture.

Conclusion

Job hunting sucks. Job hunting when you don't have a job can be demoralizing and make you question your sanity. I was fortunate enough to have 3 months of severance (thanks, European workers' rights laws!), and only needed to cover one month with the final severance + unemployment benefits. I also got a LOT of practice interviewing, and I learned exactly what I do and do not want in a job.

I wish I didn't have to go through so much frustration and rejection to learn these lessons, but it is what it is. In the end, I am employed at a place that seems like a very good fit and that will give me a chance to make an impact. I am happy.

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-28T12:06:27+01:00

This is a test for webmentions. It should only affect the HTML site.

Test 1 Test 2 Test 3 Test 4 Test 5 Test 6 Test 7 Test 8 Test 9 Test 10 Test 11 Test 12 Test 13 Test 14 Test 15 Test 16 Test 17 Test 18 Test 19 Test 20 Test 21 Test 22 Test 23

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-27T11:38:23+01:00

Partly a copy of AI Game dev journal entry

I have succeeded in removing heap allocation and dynamic dispatch from the GBNF grammar-limiting feature of the AI game. This is a mouthful way of saying that it's now easier and more performant to limit the output values of the large language model used to drive the game's content.

Benefits

The code is much easier to read and write with these changes, and it takes advantage of Rust's powerful generics to avoid unnecessary heap allocation and dynamic dispatch, which is theoretically faster to execute. Not that dynamic dispatch would be particularly slow here.

As the codebase evolves further, I hope to be able to further obfuscate away the layers of abstraction and remove cloning, making limit generation even more performant.

Technical Explanation

After writing the previous dev journal yesterday about my attempts to remove the dynamic trait objects from GBNF limit creation, I have finally succeeded in figuring out the right set of trait bounds and associated types required to make it work properly. Initial implementation of removing the dynamic trait object was very easy, but I quickly ran into an issue with how “primitives” (i.e. single values like a number or string) vs “complex” (nested types with multiple fields) are handled.

This required creating two new types:

  • GbnfLimitedPritive
  • GbnfLimitedComplex

These two wrapper structs have a bunch of fancy trait bounds and associated types on them that allow instances to be created that hold the proper limit rule for the given field which is limited. That might be hard to understand, so here is a simplified example, directly from the game code.

#[derive(Debug, Serialize, Deserialize, Clone, Gbnf)] #[derive(Debug, Serialize, Deserialize, Clone, Gbnf)]

pub struct RawCommandEvent {
    pub event_name: String,

    #[gbnf_limit_primitive]
    pub applies_to: String,

    #[gbnf_limit_primitive]
    pub parameter: String,
}

// Limit creation
let applies_to = vec!["self", "uuid1", "uuid2", "uuid3"];
let all_uuids = vec!["uuid1", "uuid2", "uuid3"];
let event_limit = RawCommandEventGbnfLimit {
    applies_to: GbnfLimitedPrimitive::new(applies_to),
    parameter: GbnfLimitedPrimitive::new(all_uuids),
};

let limit = RawCommandExecutionGbnfLimit {
    event: GbnfLimitedComplex::new(event_limit),
};

In this code, the event itself has two limited fields:

  • applies_to: The UUID of the thing in the scene that the event originates from.
  • parameter: The UUID of the thing in the scene that is affected by this event.

These are single String values. GbnfLimitedPrimitive takes a Vec of allowed values, without any heap allocation or dynamic dispatch. In contrast, the main struct has an Option field that can contain a single event. The generated GBNF limit struct mirrors the creation of the regular struct, and takes only one instance of GbnfLimitedComplex, again with no heap allocation or dynamic dispatch.

This also makes the code much easier to read, as it no longer requires a bunch of janky Box::new or into() invocations.

Next Steps

I am satisfied with the current state of GBNF grammar limiting. The next major steps for the AI game will focus on implementing a basic interactive command beyond exploring the world. This will test the limits of the events system as-is, and likely force my planned modifications to it (removal of the applies_to field and reworking of how parameters to events are handled).

This will be a stepping stone to implementing a “one-of” feature in the GBNF grammar rules generator, which will then allow hyper-specific events with their own types and specific fields.

Filed under: opensource, ai-game License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-26T19:08:31+01:00

This is a copy of a journal entry from the AI game page.

The AI game can now limit output using the gbnf_limit feature, but it requires dynamic trait objects for this. Rather than generating a so-called “limit struct” with proper concrete types, the code relies on using dynamic typing of anything that can produce a GbnfLimit. This makes the code easier to understand, but creating limit structs does is not ergonomic:

  • Lots of Box::new.
  • Performance implications of dynamic dispatch.

I am trying to fix this on a separate branch that is not yet uploaded to the Git repository, because it's a giant mess. I have made some progress, but I'm running into the limitations of Rust's (very powerful) generics system. Namely, blanket traits are not so specific: an impl for Option also counts as an impl for Option>. This can be solved by something called “trait specialization,” but that's an unstable nightly-only feature and has its own set of issues.

I have almost worked out a way to make the concrete types work. But much like the initial implementation of the GBNF grammar generator, I've sort of hit a roadblock due to trying to remove dynamic dispatch.

I've been spending my time creating gemfreely instead.

I hope to return to the AI game soon and get the dynamic dispatch fully removed from GBNF Limit code, so development on at least one interactive command can resume!

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-24T23:10:20+01:00

gemini://agnos.is/section/trackballs/

In the spirit of the small web, I have created a new section page about my favorite input device: the trackball mouse! Trackballs are a bit obscure in the modern age of touchpads and optical mice. But there is still a small yet dedicated group of computer users that use trackballs as their primary way of interacting with their machines. Personally, I switched to using a trackball along with a mouse a few years ago due to RSI and ergonomics issues. After trying 4 different trackballs, I finally found the perfect one for me.

For whatever reason, trackballs seem to be a bit more complicated when it comes to finding “the right one” compared to a mouse. Any mouse will do (though some are obviously better than others), but due to the way one holds a trackball, it needs to fit the hand “just right,” and the ball needs to move “just right,” and the buttons have to be within just the right distance.

My Perfect Trackball

My trackball of choice is the GameBall.

At risk of sounding like a shill for the GameBall, it truly is one of the best trackballs available. While its primary marketed use is gaming, it works perfectly fine for any regular computing task.

Other Brands

Other popular brands include Kensington and ELECOM. I can recommend both, although the ELECOM trackballs often have low-quality bearings that need replacing in order for the ball to move truly smoothly.

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-24T16:19:58+01:00

I have made a small update for gemfreely, and have some plans for the roadmap ahead. I've been thinking about how to implement comment sync, as well better ways of serving the blog.

🦠 Germ Update

gemfreely has been updated to 0.1.7 in order to integrate some bug fixes from the underlying Gemini protocol library: germ.

Germ – 🦠 The Definitive Gemini Protocol Toolkit

This is the library that powers Gemfreely's connection to Gemini and parsing of Gemfeeds. It's very easy to use, and has now been updated to allow better detection of MIME types (Atom vs Gemfeed, for example), and I can remove the kludge fix for emojis in title headings.

🛣️ Roadmap

The next major thing I want to add to gemfreely is the ability to sync comments coming in to the blog post from the Fediverse. This is somewhat difficult, because WriteFreely does not really do anything with replies to comments (it's a request feature). The recommended workaround is to add a separate account to the signature of the post, so that account gets notified by replies from other users. This is the path I will try first.

  • Integrate a Mastodon/Misskey API client.
  • Listen to notification stream, and write comments to Gemtext files.

I have also been considering integrating Fluffer, an experimental Gemini web app SDK for Rust. This would allow gemfreely to run in an actual server mode, and act as a proxy to WriteFreely itself. I'm not sure how I'll apply that yet, though.

Fluffer – an experimental crate that aims to make writing Gemini apps fun and easy

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-22T19:21:13+01:00

Linux-libre 5.15.151 Gentoo dist-kernel

The stable 5.15.x dist-kernel for Gentoo has updated to 5.15.151, and a new Linux-libre dist-kernel release follows it on my personal overlay.

gemini://agnos.is/projects/linux-libre-gentoo/

gemfreely 0.1.6

I have also updated gemfreely to 0.1.6:

  • Static binary releases (for Linux)!
  • Gemfeeds whose titles start with an emoji are now handled correctly.
  • Changes to internal code structure and cleanup (removal of unused files).

The most important change for in this release is that new releases are now available as statically compiled executables. You can just download and run gemfreely without building it from source using cargo.

Grab the new release here What is Gemfreely?

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-21T21:19:34+01:00

There is now a page on the capsule for the gemfreely tool.

gemini://agnos.is/projects/gemfreely/

This contains an intro to the tool and basic documentation.

License: CC-BY-SA-4.0.

Written by: @[email protected]

Published: 2024-03-21T14:22:58+01:00

The gemfreely command supports sanitizing the generated Markdown files by stripping out text before and after certain points in the file, based on a string marker. The Justfile accidentally had a space in both markers, and thus it was missing the markers in the gemtext files. This should be fixed as of this post... I hope. It's time to add some tests to gemfreely.

gemfreely (crates.io)

License: CC-BY-SA-4.0.

Written by: @[email protected]